任務取消
我們已經在 非阻擋呼叫 區段看到取消的範例。在本區段,我們將更詳細地檢視取消。
一旦一個任務分叉,你可以使用 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 讓 Generator 直接跳到 finally 區塊。你可以使用 yield cancelled()
來檢查 Generator 是否已經被取消。
取消正在執行的任務也會取消當前 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 效果測試 Generator
當 fork
被呼叫時,他會在背景中啟動任務,並且還會傳回任務物件,就像我們先前學過的。在測試時,我們必須使用工具函式 createMockTask
。此函式回傳的物件應該傳遞給 fork 測試後的下一個 next
呼叫。例如,模擬任務可以傳遞給 cancel
。以下是此頁面頂端的 main
Generator 的測試。
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)
會將其內部狀態設定為已完成,而任何給定任務的 join
效果都會傳回 42
。
在模擬任務上呼叫 setResult
、setError
或 cancel
,然後才呼叫其中一個(嘗試第二次更改其狀態),會擲回錯誤。
註解
請務必記得,yield cancel(task)
並不會等待被取消的工作完成(即執行其最終區塊)。取消效果的行為很像 fork。它會在取消發起後立即回傳。一旦被取消,工作通常會在其完成清理邏輯後立即回傳。
自動取消
除了手動取消外,也有一些會自動觸發取消的情況
在
race
效果中。除了獲勝者外,所有 race 參與競爭者都會自動被取消。在平行效果中 (
yield all([...])
)。只要其中一個子效果會被拒絕 (如同Promise.all
所暗示),平行效果就會被拒絕。在這種情況下,所有其他子效果都會自動被取消。