web-dev-qa-db-fra.com

Annuler une saga lorsqu'une action est envoyée avec redux-saga

Je démarre une minuterie pour un chronomètre React composant lorsqu'une action START est envoyée:

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
*/

J'ai du mal à arrêter la saga lorsque l'action STOP est envoyée depuis mon composant. J'ai essayé d'utiliser les effets cancel et cancelled depuis ma saga de travail:

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

ainsi que l'approche dans le premier bloc de code où j'essaie d'arrêter la saga du service de surveillance.

14
vamsiampolu

Il semble que quelques choses se passent ici:

  1. L'effet secondaire cancelprend un objet Task comme argument . Ce que vous y passez dans le code ci-dessus est juste le GeneratorFunction qui crée l'objet saga/Generator. Pour une excellente introduction aux générateurs et à leur fonctionnement, consultez cet article .
  2. Vous utilisez yield* Avant les générateurs takeEvery et takeLatest. Utiliser yield*étalera toute la séquence . Vous pouvez donc y penser comme ceci: que cela remplit la ligne

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

    avec

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

    Et je ne pense pas que c'est ce que vous cherchez, car je pense que cela finira par bloquer la deuxième ligne de votre timerTickSaga. Au lieu de cela, vous voulez probablement:

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

    Cela supprime l'effet takeEvery pour ne pas bloquer la ligne suivante.

  3. Le deuxième argument que vous passez dans takeLatest n'est qu'un objet - un objet d'effet CANCEL . Le deuxième argument de takeLatest doit en fait être un GeneratorFunction, qui sera exécuté lorsqu'une action correspondant au modèle STOP est envoyée au magasin Redux. Donc ça devrait vraiment être une fonction de saga. Vous voulez que cela annule la tâche fork(takeEvery, [START, TICK], timerTickWorkerSaga) afin que les futures actions START et TICK n'entraînent pas l'exécution de timerTickWorkerSaga. Pour ce faire, la saga doit exécuter un effet CANCEL avec l'objet Task résultant de l'effet fork(takeEvery.... Nous pouvons l'objet Task comme argument supplémentaire à la saga takeLatest. Nous nous retrouvons donc avec quelque chose comme:

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

Pour des références supplémentaires, consultez le exemple d'annulation de tâche dans les documents redux-saga. Si vous regardez dans la saga main, vous verrez comment l'effet fork produit un objet/descripteur Task qui est utilisé plus bas lors de la production de cancel effet.

12
rayd

Redux-Saga a une méthode pour cela maintenant, elle s'appelle race race. Il exécutera 2 tâches, mais lorsque l'une se terminera, elle annulera automatiquement l'autre.

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

  • watchStartTickBackgroundSaga est toujours en cours d'exécution

  • Chaque fois qu'il y a un départ ou un tick, lancez une course entre timerTickWorkerSaga et écoutez la prochaine action STOP.
  • Lorsque l'une de ces tâches se termine, l'autre tâche est annulée, c'est le comportement de la race.
  • Les noms "tâche" et "annuler" à l'intérieur de la course n'ont pas d'importance, ils aident simplement à la lisibilité du code

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

La réponse de rayd est très correcte mais un peu superflue dans la mesure où takeEvery et takeLatest en interne font un fork. Vous pouvez voir l'explication ici :

Le code devrait donc être:

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

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