dwjohnston
dwjohnston

Reputation: 11870

takeSequential in redux-saga?

I have a basic saga that looks like this:

const mySaga = function* () {
    yield takeEvery("SOME_ACTION_REQUEST", function* (action) {

        const result = yield call(makeApiCall, action.payload); 
        yield put({
            type: "SOME_ACTION_SUCCESS", 
            payload: result
        }); 

    });  
}

Now the problem I'm having is that if I have two "SOME_ACTION_REQUEST" dispatched simultaneously, then I have a situation where the redux call stack looks like:

SOME_ACTION_REQUEST
SOME_ACTION_REQUEST
SOME_ACTION_SUCCESS
SOME_ACTION_SUCCESS

And this is screwing up the logic in my reducer.

What I want, is for every request to be run, but for it to wait for the previous one to have completed before it starts.

ie. so it would look like:

SOME_ACTION_REQUEST
SOME_ACTION_SUCCESS
SOME_ACTION_REQUEST
SOME_ACTION_SUCCESS

How would I achieve this?

Upvotes: 3

Views: 1737

Answers (2)

dwjohnston
dwjohnston

Reputation: 11870

The actionChannel effect can be used to achieve this.

See: https://redux-saga.js.org/docs/api/#actionchannelpattern-buffer

const mySaga = function* () {
    const channel = yield actionChannel("SOME_ACTION_REQUEST"); 
    while (true) {
        const action = yield take(channel); 
        const result = yield call(makeApiCall, action.payload); 
        yield put({
            type: "SOME_ACTION_SUCCESS", 
            payload: result
        }); 
    }
}

Explanation:

My understanding is that the actionChannel effect is just going to route all of the incoming requests matching that pattern into a queue.

The take effect is going to pop them off.

Putting it all in a while(true) loop means that the actions will be popped off one at a time, and they need to wait to resolve all of the rest of the things (the API calls) before the next one can be called.

One thing to note about this pattern, is that in redux-dev-tools, the redux stack is still going to look like:

SOME_ACTION_REQUEST
SOME_ACTION_REQUEST
SOME_ACTION_SUCCESS
SOME_ACTION_SUCCESS

because the requests immediately are added to the channel and remain inactive until they're popped off.

Upvotes: 5

Joseph D.
Joseph D.

Reputation: 12174

You can achieve this with just two forked tasks. Like a ping-pong messaging system.

For takeEvery always create a new forked task for every action that is received.

Something like this:

function* ping() {
  while (true) {
    const { payload } = yield take("SOME_ACTION_REQUEST");
    yield put({
      type: "DO_REQUEST", 
      payload
    });
    yield take("SOME_ACTION_SUCCESS");
  }
}

function* pong() {
  while (true) {
    const { payload } = yield take("DO_REQUEST");
    const result = yield call(makeApiCall, payload); 
    yield put({
      type: "SOME_ACTION_SUCCESS", 
      payload: result
    }); 
  }
}

function* rootSaga() {
  yield all([
    fork(ping),
    fork(pong),
  ]);
}

Upvotes: 0

Related Questions