I have this type of composition of my sagas - a race between:
export function* doPollingSaga() {
try {
while (true) {
const res = yield call(fetchData, {})
if(res) {
yield put(saveResultsActionCreator(res))
if(weHaveEverythingWeWanted) {
break;
}
}
yield delay(POLLING_DELAY);
}
} catch (e) {
yield put(doSomeErrorReportingAndFinishActionCreator());
} finally {
if (yield cancelled()) {
yield put(doSomePostCancelActionCreator());
}
}
}
export default function*() {
yield race([
take(ActionTypes.CANCEL),
all([
takeLatest(ActionTypes.DO_POLLING, doPollingSaga),
// some more other sagas here
]),
]);
}
but when I dispatch ActionTypes.CANCEL
from one of the UI components - nothing happens
In redux devtools I see that it's being dispatched correctly, but polling doesn't stop, doPollingSaga
continues like nothing happened.
Inside doPollingSaga
yield cancelled() === false
all the time.
I've also tried replacing while(true)
with while(!(yield cancelled()))
but no effect as well.
From the docs I assume that everything inside all([...])
should be cancelled when ActionType.CANCEL
wins the race
Not sure if I'm missing something important about cancellation of sagas.
Looks like the cause is in nesting order of race
and takeLatest
. The latter forks given saga, making it cancel-proof. You have to either listen to cancel actions inside the polling loop, or make a race
inside the takeLatest
:
function* cancellableSaga() {
yield race([take('cancel_polling'), call(doPollingSaga)]);
}