任务的取消

我们已经在 无阻塞调用 一节中看到了取消任务的示例。
在这节,我们将回顾一下,在一些更加详细的情况下取消的语义。

一旦任务被 fork,可以使用 yield cancel(task) 来中止任务执行。取消正在运行的任务,将抛出 SagaCancellationException 错误。

来看看它是如何工作的,让我们先考虑一个简单的例子:一个可通过某些 UI 命令启动或停止的后台同步任务。
在接收到 START_BACKGROUND_SYNC action 后,我们 fork 一个后台任务,周期性地从远程服务器同步一些数据。

这个任务将会一直执行直到一个 STOP_BACKGROUND_SYNC action 被触发。
然后我们取消后台任务,等待下一个 START_BACKGROUND_SYNC action。

  1. import { SagaCancellationException } from 'redux-saga'
  2. import { take, put, call, fork, cancel } from 'redux-saga/effects'
  3. import actions from 'somewhere'
  4. import { someApi, delay } from 'somewhere'
  5. function* bgSync() {
  6. try {
  7. while(true) {
  8. yield put(actions.requestStart())
  9. const result = yield call(someApi)
  10. yield put(actions.requestSuccess(result))
  11. yield call(delay, 5000)
  12. }
  13. } catch(error) {
  14. // 或直接使用 `isCancelError(error)`
  15. if(error instanceof SagaCancellationException)
  16. yield put(actions.requestFailure('Sync cancelled!'))
  17. }
  18. }
  19. function* main() {
  20. while( yield take(START_BACKGROUND_SYNC) ) {
  21. // 启动后台任务
  22. const bgSyncTask = yield fork(bgSync)
  23. // 等待用户的停止操作
  24. yield take(STOP_BACKGROUND_SYNC)
  25. // 用户点击了停止,取消后台任务
  26. // 将抛出一个 SagaCancellationException 错误至被 fork 的 bgSync 任务
  27. yield cancel(bgSyncTask)
  28. }
  29. }

yield cancel(bgSyncTask) 将在当前执行的任务中抛出一个 SagaCancellationException 类型的异常。
在上面的示例中,异常是由 bgSync 引发的。注意,未被捕获的 SagaCancellationException 不会向上冒泡
在上面的示例中,如果 bgSync 没有捕获取消错误,错误将不会被传播到 main(因为 main 已经往前进了)。

取消正在执行的任务,也将同时取消被阻塞在当前 Effect 中的任务。

举个例子,假设在应用程序生命周期的某个时刻,还有挂起的(未完成的)调用链:

  1. function* main() {
  2. const task = yield fork(subtask)
  3. ...
  4. // later
  5. yield cancel(task)
  6. }
  7. function* subtask() {
  8. ...
  9. yield call(subtask2) // currently blocked on this call
  10. ...
  11. }
  12. function* subtask2() {
  13. ...
  14. yield call(someApi) // currently blocked on this all
  15. ...
  16. }

yield cancel(task) 将触发 subtask 任务的取消,反过来它将触发 subtask2 的取消。
subtask2 中将抛出一个 SagaCancellationException 错误,然后另一个 SagaCancellationException 错误将会在 subtask 中抛出。
如果 subtask 没有处理取消异常,一条警告信息将在控制台中打印出来,以警告开发者(如果 process.env.NODE_ENV 变量存在,并且它被设置为 development,就仅仅会打印日志信息而不是警告信息)。

取消异常的主要目的是让已取消的任务执行任何自定义的清理逻辑,因此,我们不会让应用程序状态不一致。
在上面后台同步的示例中,通过捕获取消异常,bgSync 能够发起一个 requestFailure action 至 store。
否则,store 可能会变得状态不一致(例如,等待一个被挂起的请求的结果)。

注意

记住重要的一点,yield cancel(task) 不会等待被取消的任务完成(即执行其 catch 区块)。cancel Effect 的行为和 fork 有点类似。
一旦取消发起,它就会尽快返回。一旦取消,任务通常应尽快完成它的清理逻辑然后返回。
在某些情况下,清理逻辑可能涉及一些异步操作,但被取消的任务变成了独立的进程,并且没有办法让它重新加入主控制流程(除了通过 Redux store 为其他任务发起 action。
然而,这将导致控制流变的复杂并且难以理解。更好的做法是尽快结束一个已取消的任务)。

自动取消

除了手动取消任务,还有一些情况的取消是自动触发的。

  1. race Effect 中。所有参与竞赛的任务,除了优胜者(译注:最先完成的任务),其他任务都会被取消。

  2. 并行的 Effect (yield [...])。一旦其中任何一个任务被拒绝,并行的 Effect 将会被拒绝(受 Promise.all 启发)。在这种情况中,所有其他的 Effect 将被自动取消。