Reputation: 551
I have a Saga like so (some pseudocodish).
Saga1 calls an API. Based on the result, I need to call two further APIs. If all the APIs succeed, I call onSuccess, else onFailure.
The code seems to almost work fine, but not quite. The problem I facing with yield all
is that it considered saga2 and saga3 complete as soon as the first yield put was called (see comment in saga2/3). It didn't wait for the fetch yield to finish.
I think it's partly due to my misunderstanding of what a "complete effect" means. But apart from that, I want yield all to wait until everything is done. I want any exceptions thrown by fetch in saga2/3 to be caught by catch in saga1.
saga1(action) {
const { onSuccess, onFailure } = action.payload;
try {
yield fetch...
if(response.some_condition) yield all([
put(saga2()),
put(saga3())
])
onSuccess();
}
catch(e) {
onFailure();
}
}
saga2(action) {
yield put(someaction()) // This yields
yield fetch...
}
saga3(action) {
yield put(someaction()) // This yield
yield fetch...
}
This code below is related to my comment below about catch not working
action1 () { // action2 is same
try {
yield fetch();
yield put(FINISHED_1);
}
catch(e) {
throw (e);
}
}
saga1() {
try {
yield put(action1());
yield put(action2());
yield all([
take(FINISHED_1),
take(FINISHED_2),
])
console.log("this doesn't print if exception in either action");
}
catch(e) {
console.log("this doesn't print if exception in either action");
}
finally {
console.log("this prints fine");
}
}
Upvotes: 8
Views: 9774
Reputation: 5006
1) To wait for multiple call
effects to run to completion:
yield all([
call(saga2, arg1, arg2, ...),
call(saga3, arg1, arg2, ...)
]);
2) To dispatch multiple actions and wait for their success actions to be dispatched:
yield put(action1());
yield put(action2());
yield all([
take(ACTION_1_SUCCESS),
take(ACTION_2_SUCCESS)
]);
Edits responding to comments
If you call the sagas directly with all
(#1 above), then you can catch errors conventionally
try {
yield all([
call(saga2, arg1, arg2, ...),
call(saga3, arg1, arg2, ...)
]);
} catch (e) {
// ...
}
But if a saga put
s actions that other sagas listen on, that saga does not receive those exceptions. Saga1
is not a parent of or attached to those sagas. It just dispatches actions, and some other tasks elsewhere listen and respond.
For Saga1
to be aware of errors in those sagas, the sagas should not throw errors, but instead dispatch an action with an error payload:
function* saga2(action) {
try {
const result = yield call(...);
yield put(action2Success(result));
} catch (e) {
yield put(action2Failure(e.message));
}
}
A saga that triggers saga2
(via put(action2())
) can handle success and failure:
function* saga1(action) {
yield put(action2());
yield put(action3());
const [success, failure] = yield race([
// if this occurs first, the race will exit, and success will be truthy
all([
take(ACTION_2_SUCCESS),
take(ACTION_3_SUCCESS)
]),
// if either of these occurs first, the race will exit, and failure will be truthy
take(ACTION_2_FAILURE),
take(ACTION_3_FAILURE)
]);
if (failure) {
return;
}
// ...
}
Sagas should handle exceptions and update the store with an error state, not throw errors. Throwing errors in sagas gets messy when working with saga concurrency constructs. For example, you cannot directly catch an error thrown by a fork
ed task. Also, using actions to signal saga results keeps a good event log in your store, on which other sagas/reducers can respond to. When you call
other sagas, the action that is supposed to initiate that saga (e.g. takeEvery(THE_ACTION, ...)
) doesn't get dispatched.
Upvotes: 13