跳至主要内容

任务取消

我们在非阻塞调用部分已经看到了取消的示例。在本节中,我们将更详细地回顾取消。

一旦任务被派生,您可以使用yield cancel(task)中止其执行。

为了了解它的工作原理,让我们考虑一个基本示例:一个后台同步,它可以通过一些 UI 命令启动/停止。在收到START_BACKGROUND_SYNC操作后,我们派生一个后台任务,该任务将定期从远程服务器同步一些数据。

该任务将持续执行,直到触发STOP_BACKGROUND_SYNC操作。然后我们取消后台任务,并再次等待下一个START_BACKGROUND_SYNC操作。

import { take, put, call, fork, cancel, cancelled, delay } from 'redux-saga/effects'
import { someApi, actions } from 'somewhere'

function* bgSync() {
try {
while (true) {
yield put(actions.requestStart())
const result = yield call(someApi)
yield put(actions.requestSuccess(result))
yield delay(5000)
}
} finally {
if (yield cancelled())
yield put(actions.requestFailure('Sync cancelled!'))
}
}

function* main() {
while ( yield take('START_BACKGROUND_SYNC') ) {
// starts the task in the background
const bgSyncTask = yield fork(bgSync)

// wait for the user stop action
yield take('STOP_BACKGROUND_SYNC')
// user clicked stop. cancel the background task
// this will cause the forked bgSync task to jump into its finally block
yield cancel(bgSyncTask)
}
}

在上面的示例中,bgSyncTask的取消将使用Generator.prototype.return使生成器直接跳转到finally块。在这里,您可以使用yield cancelled()来检查生成器是否已被取消。

取消正在运行的任务也会取消当前 Effect,该任务在取消时被阻塞。

例如,假设在应用程序生命周期的某个时刻,我们有以下待处理的调用链

function* main() {
const task = yield fork(subtask)
...
// later
yield cancel(task)
}

function* subtask() {
...
yield call(subtask2) // currently blocked on this call
...
}

function* subtask2() {
...
yield call(someApi) // currently blocked on this call
...
}

yield cancel(task)subtask上触发取消,进而触发subtask2上的取消。

因此我们看到取消向下传播(与返回值和未捕获的错误向上传播相反)。您可以将其视为调用者(调用异步操作)和被调用者(被调用的操作)之间的契约。被调用者负责执行操作。如果它已完成(成功或错误),结果将向上传播到其调用者,最终传播到调用者的调用者,依此类推。也就是说,被调用者负责完成流程

现在,如果被调用者仍然处于挂起状态,而调用者决定取消操作,它会触发一种信号,该信号向下传播到被调用者(以及可能被被调用者本身调用的任何深度操作)。所有深度挂起操作都将被取消。

取消还会传播到另一个方向:如果一个任务被取消,那么该任务的连接者(那些阻塞在 yield join(task) 上的)也会被取消。类似地,任何潜在的连接者调用者也会被取消(因为它们被阻塞在一个从外部被取消的操作上)。

测试带有 fork 效果的生成器

当调用 fork 时,它会在后台启动任务,并返回任务对象,就像我们之前学到的那样。在测试时,我们必须使用实用函数 createMockTask。从该函数返回的对象应该传递给 fork 测试后的下一个 next 调用。然后可以将模拟任务传递给 cancel,例如。以下是本页顶部的 main 生成器的测试。

import { createMockTask } from '@redux-saga/testing-utils';

describe('main', () => {
const generator = main();

it('waits for start action', () => {
const expectedYield = take('START_BACKGROUND_SYNC');
expect(generator.next().value).to.deep.equal(expectedYield);
});

it('forks the service', () => {
const expectedYield = fork(bgSync);
const mockedAction = { type: 'START_BACKGROUND_SYNC' };
expect(generator.next(mockedAction).value).to.deep.equal(expectedYield);
});

it('waits for stop action and then cancels the service', () => {
const mockTask = createMockTask();

const expectedTakeYield = take('STOP_BACKGROUND_SYNC');
expect(generator.next(mockTask).value).to.deep.equal(expectedTakeYield);

const expectedCancelYield = cancel(mockTask);
expect(generator.next().value).to.deep.equal(expectedCancelYield);
});
});

您可以使用模拟任务的 setResultsetErrorcancel 方法来控制其状态。例如,mockTask.setResult(42) 将将其内部状态设置为 Done,并且任何给定该任务的 join 效果将返回 42

在已经调用其中一个方法后,再次调用模拟任务的 setResultsetErrorcancel 来尝试第二次更改其状态,将会抛出错误。

注意

重要的是要记住,yield cancel(task) 不会等待被取消的任务完成(即执行其 finally 块)。取消效果的行为类似于 fork。它在取消启动后立即返回。一旦被取消,任务通常应该在完成其清理逻辑后立即返回。

自动取消

除了手动取消之外,还有一些情况下会自动触发取消。

  1. race 效果中。所有比赛竞争者,除了获胜者,都会被自动取消。

  2. 在并行效果 (yield all([...])) 中,一旦其中一个子效果被拒绝(如 Promise.all 所暗示),并行效果就会被拒绝。在这种情况下,所有其他子效果都会自动取消。