任务取消
我们在非阻塞调用部分已经看到了取消的示例。在本节中,我们将更详细地回顾取消。
一旦任务被派生,您可以使用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);
});
});
您可以使用模拟任务的 setResult
、setError
和 cancel
方法来控制其状态。例如,mockTask.setResult(42)
将将其内部状态设置为 Done,并且任何给定该任务的 join
效果将返回 42
。
在已经调用其中一个方法后,再次调用模拟任务的 setResult
、setError
或 cancel
来尝试第二次更改其状态,将会抛出错误。
注意
重要的是要记住,yield cancel(task)
不会等待被取消的任务完成(即执行其 finally 块)。取消效果的行为类似于 fork。它在取消启动后立即返回。一旦被取消,任务通常应该在完成其清理逻辑后立即返回。
自动取消
除了手动取消之外,还有一些情况下会自动触发取消。
在
race
效果中。所有比赛竞争者,除了获胜者,都会被自动取消。在并行效果 (
yield all([...])
) 中,一旦其中一个子效果被拒绝(如Promise.all
所暗示),并行效果就会被拒绝。在这种情况下,所有其他子效果都会自动取消。