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?
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.