简体   繁体   English

使用redux-saga调度操作时取消传奇

[英]Cancel a saga when an action is dispatched with redux-saga

I start a timer for a stopwatch React component when a START action is dispatched: 在调度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
*/

I have trouble stopping the saga when the STOP action is dispatched from my component. 从我的组件调度STOP动作时,我无法停止传奇。 I have tried using cancel and cancelled effects from within my worker saga: 我试过在我的工作者传奇中使用cancelcancelled效果:

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

as well as the approach in the first code block where I try and stop the saga from the watching service. 以及第一个代码块中的方法,我试图阻止来自观看服务的传奇。

Looks like a few things are going on here: 看起来这里发生了一些事情:

  1. The cancel side effect takes a Task object as its argument . cancel副作用Task对象作为其参数 What you're passing into it in the code above is just the GeneratorFunction that creates the saga/Generator object. 您在上面的代码中传递的内容只是GeneratorFunction saga / Generator对象的GeneratorFunction。 For a great intro to generators and how they work, check out this article . 有关生成器及其工作原理的详细介绍,请查看本文
  2. You're using yield* before the takeEvery and takeLatest generators. 你在takeEvery之前使用yield*并使用takeLatest生成器。 Using yield* will spread the whole sequence . 使用yield*扩展整个序列 So you can think of it like this: that it's filling in the line 所以你可以这样想:它填补了这条线

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

    with

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

    And I don't think this is what you're going for, because I believe this will end up blocking the second line of your timerTickSaga . 而且我认为这不是你想要的,因为我相信这最终会阻止你的timerTickSaga的第二行。 Instead you probably want: 相反,你可能想要:

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

    This forks off the takeEvery effect so it doesn't block the next line. 这会取消takeEvery效果,因此不会阻止下一行。

  3. The second argument you're passing into takeLatest is just an object - a CANCEL effect object . 你传递给takeLatest的第二个参数只是一个对象 - 一个CANCEL效果对象 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. takeLatest的第二个参数实际上应该是一个GeneratorFunction ,它将在匹配STOP模式的动作被调度到Redux存储时运行。 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. 您希望这取消fork(takeEvery, [START, TICK], timerTickWorkerSaga)任务,以便将来的STARTTICK操作不会导致timerTickWorkerSaga运行。 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 argument to the takeLatest saga. So we end up with something along the lines of: 你可以通过使用由fork(takeEvery...产生的Task对象运行一个CANCEL效果来实现这一点fork(takeEvery...效果。我们可以将Task对象作为takeLatest传奇的附加参数 。所以我们最终会得到一些东西。线条:

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

For additional reference check out the task cancellation example in the redux-saga docs. 有关其他参考,请查看redux-saga文档中的任务取消示例 If you look in the main saga there, you'll see how the fork effect yields a Task object/descriptor that is used further down when yielding the cancel effect. 如果你查看那里的main传奇,你会看到fork效果如何产生一个Task对象/描述符,在产生cancel效果时会进一步使用它。

The answer from rayd is very correct but a bit superfluous in the way that takeEvery and takeLatest internally are doing a fork. rayd的答案是非常正确的,但在takeEvery和takeLatest内部正在做分叉的方式有点多余。 You can see the explanation here : 你可以在这里看到解释:

So the code should be: 所以代码应该是:

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

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

Redux-Saga has a method for this now, it's called race race . Redux-Saga现在有一种方法,它叫做种族race It will run 2 tasks, but when one finishes, it will automatically cancel the other. 它将运行2个任务,但是当一个任务完成时,它将自动取消另一个任务。

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

  • watchStartTickBackgroundSaga is always running watchStartTickBackgroundSaga一直在运行

  • Every time there's a start or tick, start a race between timerTickWorkerSaga and listening for the next STOP action. 每次有开始或勾选时,在timerTickWorkerSaga和听取下一个STOP动作之间开始比赛。
  • When one of those tasks finishes, the other task is cancelled this is the behavior of race. 当其中一个任务完成时,另一个任务被取消,这就是种族的行为。
  • The names "task" and "cancel" inside of race do not matter, they just help readability of the code 种族内部的名称“任务”和“取消”并不重要,它们只是帮助代码的可读性

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM