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");
}
}
The yielded objects are a kind of instruction to be interpreted by the middleware. When a Promise is yielded to the middleware, the middleware will suspend the Saga until the Promise completes.
In this above example, we yield call . call is a blocking effect creator. This means that the saga will not continue to run to the next yield until the API call finishes. Once it's finished, we yield put . put is dispatching a new action with the result from the previous yield.
The most common takeEvery function is very similar to redux-thunk in its behaviour and methodology. It's basically a wrapper for yield take of a pattern or channel and yield fork .
Redux Saga as Middleware Redux Saga is a middleware that takes over the control of you actions before reaching the reducer directly.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With