redux-saga 的 fork 模型
在 redux-saga
中,您可以使用 2 种 Effects 动态地 fork 在后台执行的任务。
fork
用于创建附加的 forkspawn
用于创建分离的 fork
附加的 fork(使用 fork
)
附加的 fork 通过以下规则保持与其父级相连
完成
- Saga 仅在以下情况下终止:
- 它终止自己的指令主体
- 所有附加的 fork 本身都已终止
例如,假设我们有以下内容:
import { fork, call, put, delay } from 'redux-saga/effects'
import api from './somewhere/api' // app specific
import { receiveData } from './somewhere/actions' // app specific
function* fetchAll() {
const task1 = yield fork(fetchResource, 'users')
const task2 = yield fork(fetchResource, 'comments')
yield delay(1000)
}
function* fetchResource(resource) {
const {data} = yield call(api.fetch, resource)
yield put(receiveData(data))
}
function* main() {
yield call(fetchAll)
}
call(fetchAll)
将在以下情况下终止:
fetchAll
本身终止,这意味着执行了所有 3 个 Effects。由于fork
Effects 是非阻塞的,因此任务将在delay(1000)
上阻塞。两个 fork 的任务终止,即在获取所需资源并发出相应的
receiveData
操作后。
因此,整个任务将阻塞,直到延迟 1000 毫秒过去并且task1
和 task2
完成了它们的工作。
例如,假设延迟 1000 毫秒过去,而这两个任务尚未完成,那么 fetchAll
仍然会等待所有 fork 的任务完成,然后再终止整个任务。
细心的读者可能已经注意到 fetchAll
saga 可以使用并行 Effect 重写。
function* fetchAll() {
yield all([
call(fetchResource, 'users'), // task1
call(fetchResource, 'comments'), // task2,
delay(1000)
])
}
实际上,附加的 fork 与并行 Effect 具有相同的语义。
- 我们正在并行执行任务。
- 父级将在所有启动的任务终止后终止。
这同样适用于所有其他语义(错误和取消传播)。您可以通过将其视为动态并行 Effect 来理解附加的 fork 的行为。
错误传播
遵循相同的类比,让我们详细检查一下并行 Effects 中如何处理错误。
例如,假设我们有以下 Effect:
yield all([
call(fetchResource, 'users'),
call(fetchResource, 'comments'),
delay(1000)
])
上述 Effect 将在 3 个子 Effect 中的任何一个失败时立即失败。此外,未捕获的错误将导致并行 Effect 取消所有其他挂起的 Effect。例如,如果 call(fetchResource, 'users')
抛出未捕获的错误,则并行 Effect 将取消另外两个任务(如果它们仍在挂起),然后使用来自失败调用的相同错误中止自身。
类似地,对于附加的 fork,Saga 将在以下情况下中止:
其主要指令体抛出错误
其附加的分支之一引发了未捕获的错误
所以在前面的例子中
//... imports
function* fetchAll() {
const task1 = yield fork(fetchResource, 'users')
const task2 = yield fork(fetchResource, 'comments')
yield delay(1000)
}
function* fetchResource(resource) {
const {data} = yield call(api.fetch, resource)
yield put(receiveData(data))
}
function* main() {
try {
yield call(fetchAll)
} catch (e) {
// handle fetchAll errors
}
}
例如,如果在某一时刻,fetchAll
被阻塞在 delay(1000)
Effect 上,并且假设 task1
失败了,那么整个 fetchAll
任务将失败,导致
取消所有其他挂起的任务。这包括
- 主任务(
fetchAll
的主体):取消它意味着取消当前的 Effectdelay(1000)
- 其他仍然挂起的派生任务。例如,我们例子中的
task2
。
- 主任务(
call(fetchAll)
将会自身抛出一个错误,该错误将在main
的catch
体中被捕获
注意,我们能够在 main
中捕获来自 call(fetchAll)
的错误,仅仅因为我们使用的是阻塞调用。并且我们无法直接从 fetchAll
中捕获错误。这是一个经验法则,**你无法捕获来自派生任务的错误**。附加分支中的失败将导致派生父级中止(就像无法捕获并行 Effect 中的错误一样,只能通过阻塞并行 Effect 从外部捕获)。
取消
取消 Saga 会导致取消
主任务,这意味着取消 Saga 被阻塞的当前 Effect
所有仍在执行的附加分支
WIP
分离分支(使用 spawn
)
分离分支在它们自己的执行上下文中运行。父级不会等待分离分支终止。来自派生任务的未捕获错误不会向上冒泡到父级。取消父级不会自动取消分离分支(你需要显式地取消它们)。
简而言之,分离分支的行为就像使用 middleware.run
API 直接启动的根 Saga。
WIP