Effects
前一节提到所有的副作用都是简单的 JavaScript 对象,所以 Redux-Saga 提供的 Effects 函数(副作用函数)都是返回纯 JavaScript 对象的,即 Effect。Effect 用于提供给中间件解释执行的操作指令。Effect 函数都在 redux-saga/effects 包中提供。在一个 Saga 中通常使用 yield 来向中间件发出 Effect,其中最简单的方式是 yield 一个 Promise 对象。
在 Saga 中,yield 关键字右侧的表达式将被求值,值的结果将作为 yield 表达式的值返回。对于 yield 一个 Promise 对象时,Promise 对象的 resolve 结果将作为 yield 表达式的值。例如以下示例。
function* fetchContent() {
const content = yield fetch('url');
// 或者使用 call 副作用函数来书写
// 使用 call 时,中间件将会确保被调用函数的执行和 resolve 结果的响应
const content = yield call(fetch, 'url');
}
所以,在编写 Saga 时,就是通过 yield 来抛出不同的 Effect 来实现各种操作。Redux-Saga 提供的常用 Effect 函数主要有以下这些。
take(pattern),命令中间件等待指定的 Action,在发起与pattern匹配的 Action 之前,Saga 将暂停。当捕获到特殊的ENDAction 时,所有被take阻塞的 Saga 都会被终止,如果有正在运行的fork子任务,则会等待子任务终止。pattern可以是以下类型的值。- 空值或者
*,表示匹配所有发起的 Action。 - 函数,匹配
pattern(action)返回值为true的 Action。 - 字符串,相等匹配。
- 数组,以上匹配规则混合使用。
- 空值或者
take(channel),命令中间件从指定的通道中等待一条特定信息。take.maybe(pattern),与take相同,但响应ENDAction 时不自动终止 Saga。actionChannel(pattern, [buffer]),命令中间件通过一个事件通道对匹配pattern的 Action 进行排序,返回一个通道对象。通道对象可以干替代pattern在take、put等函数中使用。flush(channel),命令中间件清除通道中所有被缓存的数据,被清除的数据会被返回至 Saga。put(action),创建一个 Effect 描述信息,命令中间件向 Store 发起一个 Action。非阻塞型 Effect,向下游抛出的错误不会回到 Saga 中。put(channel, action),向指定通道中放入一个 Action。put.resolve(action),与put相同,但是阻塞型 Effect,会返回dispatch的结果并冒泡下游抛出的错误。call(fn, ...args),命令中间件以参数args调用函数fn。其中fn可以是普通函数也可以是生成器函数,中间件将会检查函数的调用结果并返回。当函数返回 Promise 时,其reject的值可以被包裹call的try...catch语句捕获。call常用格式有以下这些。call([context, fn], ...args),支持将上下文传递给fn。call([context, fnName], ...args),支持使用字符串调用fn,常用于调用对象中的方法。apply(context, fn, [args]),同call([context, fn], ...args)。
cps(fn, ...args),命令中间件以 Node 风格函数调用fn。Node 风格函数在其执行后会调用一个回调函数,回调函数的第一个参数用于报告错误,第二个参数用于报告函数执行结果。还可以使用cps([context, fn], ...args)的形式传递上下文对象给函数。fork(fn, ...args),命令中间件以非阻塞调用的方式执行fn,即派生子任务。还可以使用fork([context, fn], ...args)的形式传递上下文对象给函数。yield fork()将返回一个Task对象,可供join使用。父任务在终止时,会等待所有子任务终止。join(task),命令中间件等待task子任务的结果。cancel(task),命令中间件取消子任务执行。或者可以使用cancel(...tasks)批量取消,cancel()取消自身。spawn(fn, ...args),与fork类似,但创建一个分离的子任务,子任务与父级任务保持独立。同样可以使用spawn([context, fn], ..args)的形式传递上下文对象给函数。select(selector, ...args),命令中间件在当前 Store 的 State 上调用指定的选择器,selector为选择器函数。cancelled(),用来判断当前 Saga 是否已经被取消。通常在finally块中使用来完成取消逻辑。setContext(props),命令中间件更新其自身上下文。getContext(prop),命令中间件返回 Saga 上下文中的特定属性。
通过 fork() 和 spawn()、middleware.run() 或者 runSaga() 建立的子任务提供了以下方法来进行控制和交互。
isRunning(): boolean,检查子任务是否在运行。isCancelled(): boolean,检查子任务是否已被取消。result<T = any>(): Tundefined},获取子任务的运行结果。子任务依旧在运行时会返回undefined。error(): anyundefined},获取子任务抛出的错误。子任务依旧在运行时会返回 undefined。toPromise<T = any>(): Promise<T>,获取一个 Promise 对象,用于获取子任务的运行结果或者错误信息。cancel(): void,取消子任务运行。setContext<C extends object>(props: Partial<C>): void,设置子任务的环境上下文。
Saga 还可以被组合在一起,来并行的启动一个或者多个子任务。组合后的 Sagas 在使用 yield 执行时,与其他的 Saga 没有任何不同。Redux-Saga 提供了以下函数来对 Saga 进行组合。
race(effects),多个 Saga 竞赛执行,effects参数为一个{ label: saga() }格式的字典对象。race返回一个赢得竞赛的{ label: result }对象。当一个 Saga 赢得竞赛时,其他的 Saga 将被终止。race还可以接受一个 Saga 数组作为参数,即race([effects])。all([effects]),并行运行多个 Saga。当 Saga 并发执行时,生成器将被暂停,直到所有 Saga 都完成运行或者任意一个 Saga 抛出错误。
除此之外,在一个 Saga 中,还可以使用 yield* 来对多个 Saga 进行排序。yield* 主要用在在生成器函数中,重新抛出另一个生成器函数,所以可以用来对 Saga 的执行进行排序。