Quoc Van Tang
Quoc Van Tang

Reputation: 1193

redux saga yield all cancel other effect when one failed

I have issues with yield all in saga effect, I provide my sample code below

function* fetchData(item) {
  try {
    const data = yield call(request, url);
    yield put(fetchDataSuccess(data));
  } catch (error) {
    yield put(fetchDataFailure(error));
    throw error;
  }
}

function* fetchSummary(action) {
  try {
      yield all(
        list.map(item=>
          call(fetchData, item)
        )
      );
  } catch (error) {
    yield put(
      enqueueSnackbar({
        message: "Has Error",
        options: { variant: "error" }
      })
    );
  }
}

The logic of it is that I want to call multiple requests (some success, and some failed).

Expected: If it has failed request, the error will be caught after yield all but those success requests still continue and it should dispatch action "fetchDataSuccess" after individual success request (Promise.all can do this)

Actual: If it has failed request, the error will be caught after yield all, and then saga immediately cancel all other "fetchData" call.

Can anyone help me to achieve this logic. Thanks in advance.

Upvotes: 1

Views: 2940

Answers (1)

Linda Paiste
Linda Paiste

Reputation: 42170

The "Actual" behavior that you are describing fits with what I am seeing in your code. As soon as any error is thrown, we leave the try block and enter the catch block.

When we yield an array of effects, the generator is blocked until all the effects are resolved or as soon as one is rejected (just like how Promise.all behaves). - docs

If you want each fetch to execute then you would need to put the try/catch inside the .map. You can either map to an array of true/false values or set a value on error. Or if you don't mind having multiple snackbars you could put enqueueSnackbar inside fetchData instead of in fetchSummary.

Here's one way to do it:

// modified to return either true or false
function* fetchData(item) {
  try {
    const data = yield call(request, item);
    yield put(fetchDataSuccess({ item, data }));
    return true;
  } catch (error) {
    yield put(fetchDataFailure({ item, error }));
    return false;
  }
}

function* fetchSummary(action) {
  const results = yield all(
    action.payload.list.map((item) => call(fetchData, item))
  );
  // check if any of the results were false;
  const hasError = results.some((res) => !res);
  if (hasError) {
    yield put(
      enqueueSnackbar({
        message: "Has Error",
        options: { variant: "error" }
      })
    );
  }
}

Code Sandbox Demo

Upvotes: 3

Related Questions