I start a timer for a stopwatch React component when a START action is dispatched:
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. I have tried using cancel
and cancelled
effects from within my worker saga:
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.
Once a task is forked, you can abort its execution using yield cancel(task) .
Since you're using takeEvery , no, there's no way to dispatch a GET_USER action from within your saga without triggering an infinite loop.
Create a plain JavaScript Object to instruct the middleware that we need to dispatch some action, and let the middleware perform the real dispatch. This way we can test the Generator's dispatch in the same way: by inspecting the yielded Effect and making sure it contains the correct instructions.
If any forked task raises an uncaught error, then the parent task will abort with the child Error, and the whole Parent's execution tree (i.e. forked tasks + the main task represented by the parent's body if it's still running) will be cancelled.
Redux-Saga has a method for this now, it's called race race
. It will run 2 tasks, but when one finishes, it will automatically cancel the other.
https://redux-saga.js.org/docs/advanced/RacingEffects.html
watchStartTickBackgroundSaga is always running
export function* watchStartTickBackgroundSaga() { yield takeEvery([START, TICK], function* (...args) { yield race({ task: call(timerTickWorkerSaga, ...args), cancel: take(STOP) }) }) }
Looks like a few things are going on here:
cancel
side effect 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.You're using yield*
before the takeEvery
and takeLatest
generators. Using yield*
will spread the whole sequence. 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
. Instead you probably want:
yield fork(takeEvery, [START, TICK], timerTickWorkerSaga)
This forks off the takeEvery
effect so it doesn't block the next line.
The second argument you're passing into takeLatest
is just an object - a 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 argument to the takeLatest
saga. So we end up with something along the lines of:
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. 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With