Reputation: 1122
Dispatching a StopAction doesn't cancel the task. The successAction or errorAction are still getting dispatched even if a StopAction was called.
function* myTask(actionCreator, action) {
try {
const { cancelTask, response } = yield race({
response: call(apiPromise, action.meta.id),
cancelTask: take(StopAction.type)
});
if (cancelTask !== undefined) {
return;
}
yield put(actionCreator.makeSuccessAction(response, action.meta));
} catch (e) {
yield put(actionCreator.makeErrorAction(e, action.meta));
}
}
function* mySaga() {
yield debounceFor(
myActionCreator.loadAction.type,
myTask,
250,
myActionCreator
);
}
export function* debounceFor(pattern, saga, ms, ...args) {
function* delayedSaga(action) {
yield call(delay, ms);
yield call(saga, ...args, action);
}
let task;
while (true) {
const action = yield take(pattern);
if (task) {
yield cancel(task);
}
task = yield fork(delayedSaga, action);
}
}
Upvotes: 1
Views: 299
Reputation: 4975
I think the problem is you are calling the Stop action during the "delay" phase (that is part of debounceFor
implementation), during that phase the myTask
saga hasn't started yet and therefore when the stop action happens there is nothing listening for it yet. After that the myTask
saga finally starts but you are running the race
effect after the stop action was already dispatched and so the race effect is finished with the api response instead.
The question is how to fix that. One option would be to merge the debounce/myTask sagas together so that you can wrap both the delay/api phase into single task and have the race effect cancel whichever it is at at the moment.
Another option would be to listen for the stop action two times, once in the delay phase and then again in the api call phases you already are. The implementation of the debounce could then look like this:
export function* debounceFor(pattern, cancelPattern, saga, ms) {
function* delayedSaga(action) {
if (cancelPattern) {
let {cancelTask} = yield race({
_: delay(ms),
cancelTask: take(cancelPattern),
});
if (cancelTask) return;
} else {
yield delay(ms);
}
yield call(saga, action);
}
let task;
while (true) {
const action = yield take(pattern);
if (task) {
yield cancel(task);
}
task = yield fork(delayedSaga, action);
}
}
// ....
yield debounceFor(
myActionCreator.loadAction.type,
StopAction.type, // added cancel type
myTask,
250,
myActionCreator
);
I should also point out that latest redux-saga supports debounce natively: https://redux-saga.js.org/docs/api/#debouncems-pattern-saga-args
Though for more complex implementations (like this one probably) you will end up using custom implementation anyway.
Upvotes: 2