Seth Lutske
Seth Lutske

Reputation: 10686

Getting results from redux-saga all, even if there are failures

I have a scenario where I need to make a number of api calls in parallel. For now, the code uses redux-saga all to do this:

try {
    const results = yield all(
      someIds.map(id =>
        call(axios.delete, `/api/somewhere/items/${id}`)
      )
    );
    console.log(results);
    yield put(someSuccessFunction)
} catch(e) {
    yield put(someFailureFunction)
}

In the event that all calls are successful, results logs properly as an array of axios responses, with headers, request, status, etc. However, if even a single call fails, the code jumps to the catch block. I have no way of knowing which call failed.

I have read How to handle array of requests in redux saga, but in the examples there, it seems the accepted answer is tracking success or failure on a per-call basis. I need to know if all calls succeeded, and if so, dispatch a success action. If any calls failed, I need to know which ones failed, and dispatch a failure or partial failure action. It might look like this:

try {
    const results = yield all(
      someIds.map(id =>
        call(axios.delete, `/api/somewhere/items/${id}`)
      )
    );
    const success = results.every(result => result.status === 200);
    const failure = results.every(result => result.status !== 200);
    const partialFailure =
      results.some(result => result.status === 200) &&
      results.some(result => result.status !== 200);
    
    if (success) put(someSuccessAction);
    if (failure) put(someFailureAction);
    if (partialFailure) put(somePartialFailureAction);

} catch(e) {
    yield put(someFailureFunction);
}

But I can't seem to grasp how to retrieve the array of results when any 500 response skips us into the catch block. What is the best tactic to do this?

Upvotes: 0

Views: 1252

Answers (1)

Martin Kadlec
Martin Kadlec

Reputation: 4975

The trick is to have a separate try..catch for each request and map the successful or failed results into a structure on top of which you can then run various array operations. Essentially, it works like Promise.reflect. In this case, it could look something like this:

function* deleteById(id) {
  try {
    const result = yield call(axios.delete, `/api/somewhere/items/${id}`)
    if (result.status !== 200) throw new Error('Invalid status')
    return {v: result, status: 'fulfilled'}
  } catch (err) {
    return {e: err, status: 'rejected'}
  }
}

function* deleteSaga() {
  const results = yield all(
    someIds.map(id => call(deleteById, id))
  )
  const success = results.every(result => result.status === 'fulfilled')
  const failure = results.every(result => result.status === 'rejected')
  //...
}

I've separated the request by id into its own saga for readability, though it can be inline generator as well.

Upvotes: 2

Related Questions