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 进行排序,返回一个通道对象。通道对象可以干替代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>(): 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 的执行进行排序。