当使用 redux-saga 发送一个动作时取消一个 saga

Cancel a saga when an action is dispatched with redux-saga

我在调度 START 操作时为秒表 React 组件启动计时器:

import 'babel-polyfill'
import { call, put } from 'redux-saga/effects'
import { delay, takeEvery, takeLatest } from 'redux-saga'
import { tick, START, TICK, STOP } from './actions'

const ONE_SECOND = 1000

export function * timerTickWorkerSaga (getState) {
  yield call(delay, ONE_SECOND)
  yield put(tick())
}

export default function * timerTickSaga () {
  yield* takeEvery([START, TICK], timerTickWorkerSaga)
  yield* takeLatest(STOP, cancel(timerTickWorkerSaga))
}
/*
  The saga should start when either a START or a TICK is dispatched
  The saga should stop running when a stop is dispatched
*/

当从我的组件调度 STOP 操作时,我无法停止 saga。我尝试在我的 worker saga 中使用 cancelcancelled 效果:

if(yield(take(STOP)) {
  yield cancel(timerTickWorkerSaga)
}

以及第一个代码块中的方法,我尝试从监视服务中停止 saga。

看起来这里发生了一些事情:

  1. cancel 副作用 takes a Task object as its argument. What you're passing into it in the code above is just the GeneratorFunction that creates the saga/Generator object. For a great intro to generators and how they work, check out this article
  2. 您在 takeEverytakeLatest 生成器之前使用 yield*。使用 yield*spread the whole sequence。所以你可以这样想:它正在填充行

    yield* takeEvery([START, TICK], timerTickWorkerSaga)

    while (true) {
        const action = yield take([START, TICK])
        yield fork(timeTickWorkerSaga, action)
    }
    

    而且我认为这不是您想要的,因为我相信这最终会阻塞您 timerTickSaga 的第二行。相反,您可能想要:

    yield fork(takeEvery, [START, TICK], timerTickWorkerSaga)
    

    这分叉了 takeEvery 效果,因此它不会阻塞下一行。

  3. 您传递给 takeLatest 的第二个参数只是一个对象 - CANCEL effect object. The second argument to takeLatest should actually be a GeneratorFunction, which will be run when an action matching the STOP pattern is dispatched to the Redux store. So that should really be a saga function. You want this to cancel the fork(takeEvery, [START, TICK], timerTickWorkerSaga) task so that future START and TICK actions will not cause the timerTickWorkerSaga to run. You can achieve this by having the saga run a CANCEL effect with the Task object that resulted from the fork(takeEvery... effect. We can the Task object as an additional argumenttakeLatest 传奇。所以我们最终得到了一些类似的东西:

    export default function * timerTickSaga () {
        const workerTask = yield fork(takeEvery, [START, TICK], timerTickWorkerSaga)
        yield fork(takeLatest, STOP, cancelWorkerSaga, workerTask)
    }
    
    function* cancelWorkerSaga (task) {
        yield cancel(task)
    }
    

如需更多参考,请查看 redux-saga 文档中的 task cancellation example。如果你查看那里的 main 传奇,你会看到 fork 效果如何产生 Task object/descriptor,在产生 cancel 时进一步使用它效果。

rayd 的回答非常正确,但在内部 takeEvery 和 takeLatest 进行分叉的方式上有点多余。 可以看解释 here:

所以代码应该是:

export default function* timerTickSaga() {
    const workerTask = yield takeEvery([START, TICK], timerTickWorkerSaga);
    yield takeLatest(STOP, cancelWorkerSaga, workerTask);
}

function* cancelWorkerSaga(task) {
    yield cancel(task);
}

Redux-Saga 现在有一个方法,叫做 race race。它将 运行 2 个任务,但是当一个完成时,它会自动取消另一个。

  • https://redux-saga.js.org/docs/advanced/RacingEffects.html

  • watchStartTickBackgroundSaga 总是 运行ning

  • 每次开始或滴答时,开始 timerTickWorkerSaga 之间的竞赛并监听下一个 STOP 动作。
  • 当其中一个任务完成时,另一个任务被取消 这是种族的行为。
  • race 中的名字 "task" 和 "cancel" 无关紧要,它们只是有助于代码的可读性

export function* watchStartTickBackgroundSaga() {
  yield takeEvery([START, TICK], function* (...args) {
    yield race({
      task: call(timerTickWorkerSaga, ...args),
      cancel: take(STOP)
    })
  })
}