How to wait execution of a saga to finish in redux-saga?
Asked Answered
P

2

9

I have the following scenario:

export function* addCircle(circleApi, { payload }) {
    try {
        const response = yield apply(
            circleApi,
            circleApi.addCircle,
            [payload]
        );

        if (response.error_type) {
           yield put(addCircleFailedAction(response.error));
        } else {
            yield put(addCircleSucceededAction(response));
        }
    } catch (err) {
        console.error(err);
    }
}

export function* addTender(tenderApi, { payload }) {
    try {
       // NOTE: I want this to finish before continuing with rest of saga below.
       yield call(addCircleAction(payload.circlePayload));

       // Rest of saga removed for brevity.
    } catch (err) {
        console.error(err);
    }
}

So, basically addCircle is making an API call, and depending on its success I call the appropriate redux action. Now, inside another saga I call the action responsible for addCircle saga, and I want it to finish execution before I continue with the rest of the saga. I tried to use call, but it basically doesn't wait for the addCircle saga to finish executing. Is there any way to wait for it? I call addCircle from inside my components and I didn't have the need to wait it, but in this specific instance I have to call it inside the saga, so I really need to wait for it to finish execution, change the state of the app, so that I can use the updated state in the rest of addTender saga. Any ideas?

Pearlpearla answered 12/10, 2018 at 21:38 Comment(2)
Take a look at the following example using while (someCondition) {} redux-saga.js.org/docs/advanced/…Analysis
@AlexanderStaroselsky It's not quite clear to me how would I do it in my case. Do you have any ideas? As all the actions and sagas relevant are already seen in the code.Pearlpearla
S
9

As per your code snippet, your addCircle saga will dispatch either addCircleFailedAction or addCircleSucceededAction action creators just before it finishes execution. So we will have to wait for those action in your addTender saga.

Basically, this is what you should do. I'm just guessing your action types based on action creator names.

yield call(addCircleAction(payload.circlePayload));
yield take([ADD_CIRCLE_FAILED_ACTION, ADD_CIRCLE_SUCCEEDED_ACTION]);

// Rest of the saga

There is one edge case though. You are not dispatching any action in the catch block of your addCircle saga. Maybe you can dispatch an action called addCircleExceptionAction inside catch block and wait for it along with the other actions like this:

yield take([ADD_CIRCLE_FAILED_ACTION, ADD_CIRCLE_SUCCEEDED_ACTION, ADD_CIRCLE_EXCEPTION_ACTION]);
Stamp answered 13/10, 2018 at 2:37 Comment(3)
@terett I would not advice using that. Assume that yield call is actually yield put because yield call(generatorFunction,args) would wait for the function to finish. Now you dispatch an action and wait for what could be the result of that action or the result of any previously/later dispatched actions of the same type.Tomas
@terret actually @Tomas has good point. I also missed that yield call(addCircleAction... should be a proper call effect. Did you really want to dispatch addCircleAction for some reason? If not, @HMR's answer is most suitable.Stamp
@terett My guess would be you need 3 actions: CIRCLE_NEEDED dispatched by UI. CIRCLE_REQUESTED and CIRLE_CREATED dispatched by addCircle saga (and a result returned so addCircle can be called by other sagas). Now reducers can set loading and result while still having the option of calling addCirlce directly from other sagas (without dispatching an action).Tomas
T
1

If you are dispatching multiple actions that would trigger addRender then there is no guarantee that take(...) would actually wait for the action that resulted of the yield call.

export function* addCircle(circleApi, { payload }) {
  try {
      const response = yield apply(
          circleApi,
          circleApi.addCircle,
          [payload]
      );

      if (response.error_type) {
         yield put(addCircleFailedAction(response.error));
         return response;
      } else {
          yield put(addCircleSucceededAction(response));
          return response;
      }
  } catch (err) {
      console.error(err);
      return {err};
  }
}

export function* addTender(tenderApi, { payload }) {
  try {
     //because addCircle saga is returning something you can re use it
     // in other sagas.
     const result = yield call(addCircle,circleAPI?,payload.circlePayload);
     //check for result.error_type here
     // Rest of saga removed for brevity.
  } catch (err) {
      console.error(err);
  }
}

Your code and the accepted answer would result in an error because call does not take an action object as first argument (it does take a {context,fn} type object).

Dispatching an action and then listening to another action that may or may not have been a side effect of the action you just dispatched is bad design. You dispatch these actions asynchronously and there is no guarantee they all take the same time to complete or provide the side effect you are waiting for in the same order as they were started.

Tomas answered 15/10, 2018 at 12:53 Comment(1)
This doens't give an actual answer. According to redux-saga apply is merely an alias with a different signature. Using apply seems to also not be the answer you're hinting to, given the comments about return values. Is your answer to call a saga that does the resolution logic for you using return values?Gott

© 2022 - 2024 — McMap. All rights reserved.