ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

redux-saga 并行任务完全指南:用 `all` Effect 组合器并发执行多个 Saga 任务

redux-saga 并行任务完全指南:用 `all` Effect 组合器并发执行多个 Saga 任务 前端【免费下载链接】redux-sagaAn alternative side effect model for Redux apps项目地址https://gitcode.com/gh_mirrors/re/redux-saga点击查看免费下载yield语句虽然能以线性、同步的书写风格描述异步控制流但它天然是逐条阻塞的——连续两次yield call(...)只会串行执行。本文聚焦 redux-saga 的 Effect 组合器all讲解如何将多个 effect 并行运行、如何收集全部结果、如何处理快速失败与 END 终止并结合本仓库packages/core的源码实现effectRunnerMap.js、io.js、utils.js与官方测试用例剖析其底层工作原理帮助你写出真正可并发的 Saga。为什么连续yield只能串行执行在 Saga 中yield会暂停生成器直到被 yield 的 effect 被中间件解析完毕next()才会继续推进。因此下面这段代码是错误的写法——第二个 effect 必须等待第一个 effect 完成后才会开始执行// 错误两个 effect 会按顺序串行执行 const users yield call(fetch, /users) const repos yield call(fetch, /repos)fetch(/repos)要等到fetch(/users)返回结果之后才会发起请求两个网络请求的总耗时是两次请求耗时之和。如果这两次请求彼此独立、没有数据依赖这种写法就白白浪费了并发能力。使用all([...])并行执行多个 Effect正确的做法是把多个 effect 放进一个数组中交给all组合器import { all, call } from redux-saga/effects // 正确多个 effect 会并行执行 const [users, repos] yield all([ call(fetch, /users), call(fetch, /repos), ])all接收一个 effect 数组中间件会同时启动其中的每一个 effect而不是按顺序等待生成器会被阻塞直到所有effect 都成功 resolve此时yield表达式返回一个数组元素顺序与传入数组一一对应users对应第一个结果repos对应第二个如果其中任意一个effect 被 reject生成器会立即恢复并抛出该错误——即快速失败。从行为语义上看yield all([...])与标准Promise.all([...])完全一致官方文档 API.md 中也明确指出它是Promise#all的对应 API。本仓库的官方测试 packages/core/tests/interpreter/all.js 验证了这一点将一个 Promise、一个cps回调式调用和一个take放入同一数组并行执行三者全部完成后结果按序返回[1, 2, {type: action}]。并行的不只是call任何 Effect 都可以组合all数组中的元素可以是任意 effectcall、cps、take、put、select甚至嵌套的all与fork。例如测试用例中就把三种不同类型的 effect 放进了同一个数组function* genFn() { actual yield all([ def.promise, // 直接 yield 一个 Promise cps(cps, 2), // Node 风格回调函数 take(action), // 等待某个 action 被 dispatch ]) }只有当 Promise 被 resolve、CPS 回调被调用、且action被 dispatch 之后这个yield all才会返回[1, 2, {type: action}]。对象形式all({...})为结果命名all除了接收数组还支持传入一个标签 → effect的字典对象返回结果同样是一个对象键与传入的标签一一对应无需依赖数组下标import { all, call } from redux-saga/effects function* mySaga() { const { customers, products } yield all({ customers: call(fetchCustomers), products: call(fetchProducts), }) }这与race(effects)的命名方式保持一致参见 API.md。官方测试 packages/core/tests/interpreter/all.js 专门覆盖了这种named effects场景{ ac: take(action), prom: promise }全部完成后返回{ ac: action, prom: 1 }。对象形式特别适合结果语义清晰、后续还要按名字取用的场景也让代码可读性更强。源码级原理all在中间件内部是如何工作的要真正理解all需要沿三条代码路径追溯其实现。1. 创建 Effect 描述对象all在 packages/core/src/internal/io.js 中只是一个描述符工厂它并不执行任何逻辑只返回一个带有[IO]标志的纯对象export function all(effects) { const eff makeEffect(effectTypes.ALL, effects) eff.combinator true return eff }combinator true标记它是一个组合器 effect这与race一致见同文件race实现目的是让类型系统与中间件能够把它和普通 effect 区分开。这正是 redux-saga 声明式 effect 思想的体现Saga 只 yield 描述由中间件负责执行参见 docs/basics/DeclarativeEffects.md 与 docs/basics/Effect.md。2. 并行启动runAllEffect中间件内部维护着一张effectRunnerMap见 packages/core/src/internal/effectRunnerMap.js其中ALL类型对应runAllEffectfunction runAllEffect(env, effects, cb, { digestEffect }) { const effectId currentEffectId const keys Object.keys(effects) if (keys.length 0) { cb(is.array(effects) ? [] : {}) return } const childCallbacks createAllStyleChildCallbacks(effects, cb) keys.forEach((key) { digestEffect(effects[key], effectId, childCallbacks[key], key) }) }关键点对空数组/空对象直接以[]或{}完成不产生任何等待官方测试 packages/core/tests/interpreter/all.js 验证空数组返回[]通过createAllStyleChildCallbacks为每个子 effect 创建一个独立回调同步遍历所有键并逐个digestEffect中间件逐个启动它们——这正是并行发生的时刻所有子 effect 在同一轮中被派发执行而不是等待前一个完成后再启动下一个。digestEffect定义在 packages/core/src/internal/proc.js它负责把 effect 分发给对应的 runner是组合器驱动子任务的入口。3. 收集结果与快速失败createAllStyleChildCallbacks结果收集逻辑在 packages/core/src/internal/utils.js 的createAllStyleChildCallbacks中这是理解all行为的关键function checkEnd() { if (completedCount totalCount) { completed true parentCallback(results) } } keys.forEach((key) { const chCbAtKey (res, isErr) { if (completed) return if (isErr || shouldComplete(res)) { parentCallback.cancel() // 快速失败取消其余子任务 parentCallback(res, isErr) } else { results[key] res completedCount checkEnd() } } ... })从中可以得出三条确凿的行为规则全部完成才返回内部用completedCount计数只有子 effect 全部成功时才会把结果数组/对象回传给生成器任一失败立即终止只要有子 effect 以错误结束isErr为真立即触发父回调并取消其余仍在运行的子任务——对应文档所述as soon as one is rejectedEND 也是一种完成信号shouldComplete(res)会检查TERMINATE/TASK_CANCEL定义在同文件 utils.js。也就是说如果all中的某个take在 channel 关闭时收到了END信号整个all会被视为终止而不是继续等待。官方测试 packages/core/tests/interpreter/all.js 验证了这一点all([promise, take(action)])在 dispatchEND后生成器进入finally块结束。4. 与race的本质区别对比 effectRunnerMap.js 中的runRaceEffectrace在第一个子 effect 完成时立即返回该结果并自动取消其余落败的 effect即谁先完成听谁的而all必须等待全部子 effect 完成全部完成才算完成。二者分别对应Promise.race与Promise.all的语义具体用法可参考 docs/advanced/RacingEffects.md。实战用all并行启动多个后台任务一个非常典型的用法是在根 Saga 中并行 fork 多个独立监听器。仓库示例 examples/shopping-cart/src/sagas/index.js 就是这么做的export default function* rootSaga() { yield all([ fork(getAllProducts), // 加载商品数据 fork(watchGetProducts), // 监听商品请求 fork(watchCheckout), // 监听结算请求 ]) }这里all([fork(...), fork(...), fork(...)])一次性并行启动三个相互独立的后台任务fork 是非阻塞的参见 docs/advanced/NonBlockingCalls.md根 Saga 会一直持有它们直到三者全部结束。这种并行启动 统一收口的模式在真实项目中极为常见。另一处参考是 examples/real-world/sagas/index.js同样用yield all([...])组合多个fork。错误处理与注意事项快速失败的代价由于all具备Promise.all式的快速失败语义任何一个子 effect 抛错都会中断整个组合其余仍在进行的请求会被取消。如果希望某个失败不影响其他任务要么用try/catch包住单个 effect要么改用spawn/fork独立任务或配合race与cancelled()实现更细粒度的容错参考 docs/basics/ErrorHandling.md 与 docs/advanced/TaskCancellation.md。空数组是合法的yield all([])立即返回[]不会阻塞生成器测试 packages/core/tests/interpreter/all.js 已覆盖可以安全地在动态拼接 effect 列表的场景中使用。结果顺序与输入顺序一致数组形式下无论各请求完成先后返回数组的下标始终对应传入时的下标不会出现结果错位。不要与嵌套all混淆all内部还可以嵌套all组合器支持递归但要注意嵌套会引入阶段化等待——内层all全部完成后外层才继续收集这通常用于先并行 A、B再并行 C、D的分阶段场景。并行并不等于线程Saga 的并行是异步并发所有 effect 仍在同一个 JavaScript 线程中通过事件循环调度不会带来多线程加速它的价值在于同时发起多个 I/O 操作以缩短总等待时间。小结连续yield call(...)是串行执行独立任务请改用all组合器yield all([e1, e2, ...])并行执行并返回按序结果数组行为等价于Promise.allyield all({label: effect})返回带标签的结果对象语义更清晰中间件通过runAllEffecteffectRunnerMap.js同步派发所有子 effect并用createAllStyleChildCallbacksutils.js实现全部完成才返回、任一失败即取消的收口逻辑根 Saga 中常用yield all([fork(a), fork(b), fork(c)])并行启动多个后台任务。相关文档可继续阅读 docs/API.md、docs/advanced/ComposingSagas.md、docs/advanced/ForkModel.md并通过官方测试 packages/core/tests/interpreter/all.js 验证上述所有行为。赞分享前端【免费下载链接】redux-sagaAn alternative side effect model for Redux apps项目地址https://gitcode.com/gh_mirrors/re/redux-saga点击查看免费下载相关推荐Redux-Saga并发模式并行任务执行与结果聚合策略Redux Saga并发模式并行任务执行与结果聚合策略 你是否在处理复杂异步逻辑时遇到过以下困境多个API请求串行执行导致页面加载缓慢重复点击按钮触发冗余前端Redux-Saga进阶指南Saga的组合与并行控制Redux Saga进阶指南Saga的组合与并行控制 引言为什么需要Saga的组合与并行控制 在现代前端应用中复杂的异步逻辑处理已成为常态。你是否遇到过前端Redux-Saga高级模式任务管理与并发控制Redux Saga高级模式任务管理与并发控制 本文深入探讨Redux Saga的高级并发控制模式重点分析fork与spawn的任务创建与生命周期管理、任务前端上一篇ai-engineering-from-scratch 如何使用 Batch API 降低非交互式 LLM 负载的推理成本下一篇模型微调实战如何用自有数据优化Hy-MT1.5-1.8B-2bit翻译质量创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表