Redux-saga如何停止或取消任务链?
如何彻底停止你的异步递增Saga
嘿,我来帮你搞定这个问题!你现在遇到的情况其实是Redux Saga里一个很常见的细节:取消根任务不会自动终止已经fork出去的子任务,而且takeEvery的工作方式也让新任务可能在你以为停止后还被创建。
为什么你的代码没生效?
先拆解下你当前的问题:
- 你调用
task.cancel()确实会取消根sagaincrementCounterSaga,连带取消它fork的incrementAsyncWatcher。但如果在取消之前已经有incrementAsync任务在跑(比如之前触发过ASYNC_INCREMENT),这个已经启动的异步任务会继续执行完3秒的延迟,然后照样触发计数器递增。 - 另外你代码里的
store.dispathch是拼写错误(应该是store.dispatch),不过看你描述3秒后saga还在运行,应该是之前已经启动的任务没被终止。
两种正确的停止方案
方案一:手动跟踪所有子任务(适合需要同时运行多个任务的场景)
takeEvery本质上是每次收到action就fork一个新任务,但它没给我们提供批量取消这些任务的接口。所以我们可以自己实现类似takeEvery的逻辑,同时保存所有子任务的引用:
function* incrementAsync() { yield call(delay, 3000); yield put(counterIncrement()); } // 用数组保存所有fork出来的异步任务 const runningAsyncTasks = []; function* incrementAsyncWatcher() { while (true) { // 等待ASYNC_INCREMENT action yield take(ASYNC_INCREMENT); // 启动新任务并把引用存起来 const task = yield fork(incrementAsync); runningAsyncTasks.push(task); } } function* incrementCounterSaga() { yield fork(incrementAsyncWatcher); } const rootTask = sagaMiddleware.run(incrementCounterSaga);
当你需要停止所有相关任务时,要做三件事:
// 1. 取消所有正在运行的异步递增任务 runningAsyncTasks.forEach(task => task.cancel()); // 2. 清空任务列表,避免下次重复取消 runningAsyncTasks.length = 0; // 3. 取消根任务,让watcher停止接收新的action rootTask.cancel(); // 现在再dispatch ASYNC_INCREMENT,watcher已经停了,不会创建新任务 store.dispatch({ type: ASYNC_INCREMENT });
方案二:用takeLatest替代takeEvery(适合只关心最新任务的场景)
如果你不需要同时运行多个异步递增任务(比如用户连续点击时,只执行最后一次的递增),takeLatest会更省心——它会自动取消之前未完成的任务,而且当你取消根任务后,它会直接停止接收新的action:
function* incrementAsync() { yield call(delay, 3000); yield put(counterIncrement()); } function* incrementAsyncWatcher() { // takeLatest会自动取消之前未完成的incrementAsync任务 yield takeLatest(ASYNC_INCREMENT, incrementAsync); } function* incrementCounterSaga() { yield fork(incrementAsyncWatcher); } const rootTask = sagaMiddleware.run(incrementCounterSaga);
停止的时候只需要一行代码:
rootTask.cancel(); // 之后dispatch的ASYNC_INCREMENT不会被处理,且之前的任务会被立即取消 store.dispatch({ type: ASYNC_INCREMENT });
额外的小技巧
- 别忘了给
incrementAsync加上取消后的清理逻辑(如果需要的话):function* incrementAsync() { try { yield call(delay, 3000); yield put(counterIncrement()); } finally { if (yield cancelled()) { console.log('异步递增任务被取消啦~'); // 这里可以写一些清理代码,比如清除定时器(不过delay用的是Promise,saga会自动处理) } } } - 确保你的
delay函数是正确实现的:const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
内容的提问来源于stack exchange,提问作者Vadim
相关产品推荐
相关产品推荐

