Search code examples
reactjsreduxredux-saga

Redux Saga pending/success/failure pattern - how to handle interruption


I am using redux-saga and @reduxjs/toolkit. I make some webservice requests and save their responses into redux, and I am following a pending/success/failure pattern I've seen mentioned in a few places. For my redux saga:

export default function* rootSaga() {
    yield takeEvery(actionGetData.toString(), handleGetData);
}

with my generator function:

function* handleGetData(action: any): Generator<any, any, any> {
    yield put({ type: 'ACTION.GETDATA_PENDING', payload: action.payload });
    try {
        const response = yield call(apiRequest);
        const responseData = response.data;
        yield put({ type: 'ACTION.GETDATA_FULFILLED', payload: { name: action.payload, data: responseData } });
    } catch (error: any) {
        yield put({ type: 'ACTION.GETDATA_FAILED', payload: { name: action.payload, data: error.message } });
    }
}

I save this status (pending, success, failure) into redux, and there is a visual component on the screen that corresponds to this status-- a loading animation, or a success/failure notification, etc.

This all works great, but the issue is that when this apiRequest takes a while to complete and the user prematurely cancels the request-- say, by closing the page before the webservice request returns a response. In this case, when the user reloads the page, the status is stuck in a "loading" state, except this time, no apiRequest will be made, so there is no way to exit this state with a success or failure, so a never-ending loading animation is shown to the user, and there is nothing to retry the webservice request.

How can I resolve this? Is there a way to call a cleanup function that can be executed before the function exits (e.g. immediately call the 'failed' action if the page is prematurely stopped)? Is there a way to 'watch' this 'pending' status and trigger a retry if it has been stuck for a certain amount of time?


Solution

  • To solve this, I ended up adding a check for pending and rejected webservice requests to occur at the start of my root saga:

    function* handlePendingRequestsSaga() {
        yield put({ type: actionClearPendingDataRequests.toString() });
        yield put({ type: actionRetryRejectedDataRequests.toString() });
    }
    
    export default function* rootSaga() {
        yield handlePendingRequestsSaga();
        yield takeEvery(actionGetData.toString(), handleGetResource);
    }
    

    And in the corresponding reducers, the one for actionClearPendingDataRequests takes the requests that were marked pending and moves them to rejected, while the one for actionRetryRejectedDataRequests will retry the requests that were previously marked rejected. Because handlePendingRequestsSaga fires on initialization (say, when a page is refreshed or first loaded), any requests that were interrupted will be marked rejected and automatically retried, and any requests that have previously failed will also be retried (once). If those fail again without interruption, the page workflow is followed as intended (show error message to user for failed request(s), etc).