今日学习目录
一、race进一步讨论
二、组合saga
三、取消任务caceled进一步讨论
一、race进一步讨论
有时候我们同时启动多个任务,但又不想等待所有任务完成,我们只希望拿到 胜利者:即第一个被 resolve(或 reject)的任务。 race Effect 提供了一个方法,在多个 Effects 之间触发一个竞赛(race)。
// 1. 触发一个远程的获取请求,并且限制了 1 秒内响应,否则作超时处理。
import { race, call, put } from 'redux-saga/effects'
import { delay } from 'redux-saga'
function* fetchPostsWithTimeout() {
const {posts, timeout} = yield race({
posts: call(fetchApi, '/posts'),
timeout: call(delay, 1000)
})
if (posts)
put({type: 'POSTS_RECEIVED', posts})
else
put({type: 'TIMEOUT_ERROR'})
}
// 2. race 的另一个有用的功能是,它会自动取消那些失败的 Effects。
import { race, take, call } from 'redux-saga/effects'
function* backgroundTask() {
while (true) { ... }
}
function* watchStartBackgroundTask() {
while (true) {
yield take('START_BACKGROUND_TASK')
yield race({
task: call(backgroundTask),
cancel: take('CANCEL_TASK')
})
}
}
二、 组合Sagas
虽然使用 yield* 是提供组合 Sagas 的惯用方式,但这个方法也有一些局限性
-
你可能会想要单独测试嵌套的 Generator。这导致了一些重复的测试代码及重复执行的开销。 我们不希望执行一个嵌套的 Generator,而仅仅是想确认它是被传入正确的参数来调用。
-
更重要的是,yield* 只允许任务的顺序组合,所以一次你只能 yield* 一个 Generator。
// 1. 直接使用 yield 来并行地启动一个或多个子任务
// 当 yield 一个 call 至 Generator,Saga 需要等待 Generator 处理结束,
// 然后以返回的值恢复执行(或错误从子任务中传播过来,则抛出异常)
function* fetchPosts() {
yield put( actions.requestPosts() )
const products = yield call(fetchApi, '/products')
yield put( actions.receivePosts(products) )
}
function* watchFetch() {
while ( yield take(FETCH_POSTS) ) {
yield call(fetchPosts) // 等待 fetchPosts 完成
}
}
// 2. yield 一个队列的嵌套的 Generators,将同时启动这些子 Generators(sub-generators),
// 并等待它们完成。 然后以所有返回的结果恢复执行:
function* mainSaga(getState) {
const results = yield [call(task1), call(task2), ...]
yield put(showResults(results))
}
// 3. 使用 effect 合并器将那些 Sagas 和所有其他类型的 Effect 合并。
function* game(getState) {
let finished
while(!finished) {
// 必须在 60 秒内完成
const {score, timeout} = yield race({
score: call( play, getState),
timeout: call(delay, 60000)
})
if (!timeout) {
finished = true
yield put(showScore(score))
}
}
}
三、取消任务进一步讨论
. 一旦任务被 fork,可以使用 yield cancel(task) 来中止任务执行。取消正在运行的任务
example:
一个可通过某些 UI 命令启动或停止的后台同步任务。 在接收到 START_BACKGROUND_SYNC action 后,我们 fork 一个后台任务,周期性地从远程服务器同步一些数
这个任务将会一直执行直到一个 STOP_BACKGROUND_SYNC action 被触发。 然后我们取消后台任务,等待下一个 START_BACKGROUND_SYNC action。
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) ) {
// 启动后台任务
const bgSyncTask = yield fork(bgSync)
// 等待用户的停止操作
yield take(STOP_BACKGROUND_SYNC)
// 用户点击了停止,取消后台任务
// 这会导致被 fork 的 bgSync 任务跳进它的 finally 区块
yield cancel(bgSyncTask)
}
}
// 取消 bgSyncTask 将会导致 Generator 跳进 finally 区块。
// 可使用 yield cancelled() 来检查 Generator 是否已经被取消。
2. 取消正在执行的任务,也将同时取消被阻塞在当前 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 的取消。
3. 关于自动取消
除了上述的一些手动取消外,还存在一些自动取消的案例
- 在race Effect中,除了最先完成任务的,其他都会被取消
- 并行的Effect( yield [...] ),一旦其中的任何一个任务被拒绝了,并行的Effect将会被拒绝(受Promise.all启发)