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 将暂停。当捕获到特殊的 END Action 时,所有被 take 阻塞的 Saga 都会被终止,如果有正在运行的 fork 子任务,则会等待子任务终止。pattern 可以是以下类型的值。
    • 空值或者 *,表示匹配所有发起的 Action。
    • 函数,匹配 pattern(action) 返回值为 true 的 Action。
    • 字符串,相等匹配。
    • 数组,以上匹配规则混合使用。
  • take(channel),命令中间件从指定的通道中等待一条特定信息。
  • take.maybe(pattern),与 take 相同,但响应 END Action 时不自动终止 Saga。
  • actionChannel(pattern, [buffer]),命令中间件通过一个事件通道对匹配 pattern 的 Action 进行排序,返回一个通道对象。通道对象可以干替代 patterntakeput 等函数中使用。
  • 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 的值可以被包裹 calltry...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>(): T undefined},获取子任务的运行结果。子任务依旧在运行时会返回 undefined
  • error(): any undefined},获取子任务抛出的错误。子任务依旧在运行时会返回 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 的执行进行排序。