redux-sagaでtakeEveryをcancelする

takeEveryで動かしている処理をcancelしたい場合、raceを使って停止させます。

/**
 * Actions
 **/
const START = 'START' as const
const CANCEL = 'CANCEL' as const
const start = () => ({
 type: START,
})
const cancel = () => ({
 type: CANCEL
})

function* startSaga() {
  // Do something
}

function* exampleSaga() {
  yield takeEvery([START], function*(...args) {
    yield race({
      task: call(startSaga, ...args),
      cancel: take(CANCEL)
    })
  }
}

これは、raceで何もしない処理と同時実行することで、「何もしない処理が先に完了したので、処理を完了とする」判定を出すやり方で、Automatic cancellationとしてドキュメントにも紹介されています。

In a race effect. All race competitors, except the winner, are automatically cancelled.

https://redux-saga.js.org/docs/advanced/TaskCancellation/#automatic-cancellation

ラッパー関数を作るなら

頻繁にCancelの必要があるtakeEveryを書く場合は、以下のようなラッパーを用意しておくと便利です。

import { takeEvery, call, race, take } from 'redux-saga/effects';

/**
 * Cancelable takeEvery
 * @example
 * ```
 * yield takeEveryWithCancel({
 *   actionNames: 'START',
 *   callback: taskSaga
 * }, 'STOP')
 * ``` 
 */
function* takeEveryWithCancel(task: {
  actionNames: string | string[];
  callback: (...args) => void
}, cancelActionName: string) {
  yield takeEvery(
    task.actionNames,
    function* (...args) {
      yield race({
        task: call(task.callback, ...args),
        cancel: take(cancelActionName),
      });
    }
  );
}

使う時はこんな感じで書きます。

yield takeEveryWithCancel({
  actionNames: START,
  callback: startSaga
}, STOP)

参考

Comment