Отменить сагу, когда действие отправляется с redux-saga

я запускаю таймер для компонента реакции секундомера, когда отправляется действие запуска:

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 действие отправляется из моего компонента. Я пробовал использовать cancel и cancelled эффекты из моей рабочей саги:

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

а также подход, в первом блоке кода, где я пытаюсь остановить саги от просмотра услуги.

3 ответов


похоже, что здесь происходит несколько вещей:

  1. на cancel побочный эффект принимает Task объект в качестве аргумента. То, что вы передаете в него в коде выше, - это просто GeneratorFunction это создает объект saga / Generator. Для большого вступления в генераторы и как они работают, проверьте в этой статье.
  2. вы используете yield* до takeEvery и takeLatest генераторы. Используя yield* будет распространение вся последовательность. Поэтому вы можете думать об этом так: что он заполняет строку

    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 - это просто объект -отменить объект эффекта. Второй аргумент к takeLatest должно быть GeneratorFunction, который будет запущен, когда действие, соответствующее STOP шаблон отправляется в магазин вместе. Так что это действительно должна быть функция saga. Вы хотите, чтобы это отменить fork(takeEvery, [START, TICK], timerTickWorkerSaga) задач, так что будущее START и TICK действия не вызывают timerTickWorkerSaga запустить. Вы можете достичь этого, имея сагу запустить CANCEL С Task объект, возникший в результате 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)
    }
    

для дополнительной справки проверьте пример отмены задачи в документах redux-saga. Если вы посмотрите в main сага там, вы увидите, как fork эффект дает Task объект / дескриптор, который используется ниже при подаче the cancel эффект.


ответ от rayd очень правильный, но немного лишний в том, как takeEvery и takeLatest внутренне делают вилку. Вы можете увидеть объяснение здесь:

поэтому код должен быть:

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 всегда работает

  • каждый раз, когда есть старт или ТИК, начать гонку между timerTickWorkerSaga и слушать для следующей остановки действие.
  • когда одна из этих задач завершается, другая задача отменяется это поведение расы.
  • названия "задача" и "отмена" внутри гонки не имеют значения, они просто помогают читаемости кода

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