一文吃透 Effect 结构化并发:forkChild、forkDetach、forkScoped 三大纤维 API 完整指南
【免费下载链接】effect-smolCore libraries and experimental work for Effect v4项目地址: https://gitcode.com/GitHub_Trending/ef/effect-smol
effect-smol是 Effect v4 的核心库与实验性代码仓库,其中"结构化并发(structured concurrency)"是它最核心的能力之一:通过forkChild、forkDetach、forkScoped三个 fork 系列 API,你可以把耗时任务派发到独立纤维(Fiber)上并行执行,同时确保后台任务不会"失控泄漏"。本文将用新手友好的方式,带你快速掌握这三个 API 的区别、选择方法和常用配置项。
什么是 Effect 结构化并发
传统并发容易出现"孤儿线程":主流程结束了,后台任务还在偷偷运行,出了问题无处追踪。Effect 的结构化并发反其道而行——每个子任务都必须明确声明自己"活在谁的生命周期里":
- 父任务结束 → 子任务自动被中断(防泄漏)
- 作用域(Scope)关闭 → 作用域内的纤维一起收尾
- 需要长期运行 → 显式声明"分离",挂到全局作用域
核心思想就一句话:并发可以随意开,但结束时总有人负责清理。
官方在 Effect.ts 文件头注释里就把 "structured concurrency" 列为库的四大支柱之一。
四个 Fork API 一张表看懂
| API | 生命周期归属 | 典型场景 | 源码位置 |
|---|---|---|---|
forkChild | 父纤维(子作用域) | 主流程内的并行子任务 | Effect.ts#L8546 |
forkScoped | 当前Scope | 随作用域一起开/关的任务 | Effect.ts#L8642 |
forkIn | 你指定的任意Scope | 把任务挂到别的作用域 | Effect.ts#L8591 |
forkDetach | 全局作用域 | 日志、心跳等守护进程 | Effect.ts#L8692 |
💡 记忆口诀:Child 跟父走,Scoped 跟作用域走,Detach 谁也不跟。
forkChild 详解:带自动监督的子纤维
forkChild是最高频的 API。它把 Effect 派发到新纤维执行,父纤维结束时,子纤维自动被中断——官方称之为"auto supervision(自动监督)",从根本上杜绝纤维泄漏。
const program = Effect.gen(function*() { const fiber = yield* longTask.pipe(Effect.forkChild) // 立即返回 Fiber // 主流程继续做别的事…… const result = yield* Fiber.join(fiber) // 需要结果时再等待 return result })文档中还特别提醒:如果只是想要"两个任务并行,取两者结果"这类常见模式,优先用更高层的组合子(如zipPar、raceWith),直接裸用forkChild反而是最后选择。完整的官方示例就写在 forkChild 定义上方 的注释里。
⚠️ 新手最容易踩的坑:fork 不等于 join。forkChild只是"点火",如果不join它,主流程不会等它完成。
forkScoped 详解:与当前作用域同生共死
forkScoped把纤维挂到当前Scope上:作用域关闭时,纤维自动被中断。它比forkChild多一个Scope环境依赖,适合"任务的生命周期由外部容器决定"的场景,例如 HTTP 请求处理期间启动的轮询、缓存刷新任务——请求结束,Scope 关闭,任务随之清理。
const program = Effect.scoped( Effect.gen(function*() { const fiber = yield* backgroundTask.pipe(Effect.forkScoped) yield* Effect.sleep("1 second") // 函数返回、Scope 关闭时,fiber 自动被中断 return "scope completed" }) )官方示例见 forkScoped 注释。
forkDetach 详解:独立于主流程的守护进程
forkDetach把纤维挂到全局作用域——主流程结束后,它依然继续运行。这是唯一"逃逸"出结构化并发的官方出口,适合心跳上报、日志守护、全局定时器等真正需要长期存活的后台任务。
const fiber = yield* daemonTask.pipe(Effect.forkDetach) // 主流程 return 后,daemon 仍在后台运行🎯选择建议:拿不准时默认用forkChild;只有当任务确实在业务主流程结束后仍必须存活时,才用forkDetach——它是 v3 中forkDaemon的新名字,语义就是"脱离父级生命周期"。
通用 Fork 配置项:startImmediately 与 uninterruptible
v4 中四个 fork API 都支持统一选项对象:
startImmediately:设为true时新纤维立即开始执行,否则默认延迟启动;uninterruptible:true使纤维不可被中断,"inherit"继承父纤维的中断策略。
yield* task.pipe(Effect.forkChild({ startImmediately: true }))这个改动在 v4 迁移文档中有完整说明,详见 migration/forking.md。
拿到结果:Fiber.join 与 Fiber.await 怎么选
fork 之后,你手里是一个Fiber值,等待结果有两个标准入口:
| 方法 | 返回值 | 适用场景 |
|---|---|---|
Fiber.join(fiber) | 任务的结果(失败则抛出) | 主流程需要结果 |
Fiber.await(fiber) | Exit值(成功/失败都拿到) | 只做观测、不传播错误 |
Fiber.join定义于 Fiber.ts#L272,Fiber.await则返回Exit类型,适合"我只想看它成没成功,不影响主流程成败"的监控型任务。
另外,Effect.awaitAllChildren可以强制当前 Effect 等所有子纤维完成后再结束,定义见 Effect.ts#L8731。
v3 迁移速查:这些 API 改名了
如果你从 Effect v3 迁移过来,重点看这张改名对照表(完整版在 migration/forking.md):
| v3 | v4 |
|---|---|
Effect.fork | Effect.forkChild |
Effect.forkDaemon | Effect.forkDetach |
Effect.forkScoped | 不变 |
Effect.forkAll | 已移除,改用逐个forkChild |
延伸阅读与源码地图
- API 定义总入口:packages/effect/src/Effect.ts("Supervision & Fibers" 区块)
- 纤维与等待操作:packages/effect/src/Fiber.ts
- v3→v4 迁移指南目录:migration/
- 可运行示例:ai-docs/src/01_effect/
- 官方测试用例(fork 行为的最佳参考):packages/effect/test/Effect.test.ts
小结
- 默认选
forkChild:子纤维跟随父级,自动监督零泄漏; - 作用域边界选
forkScoped:和 Scope 同生共死; - 真·后台任务才选
forkDetach:挂全局作用域,主流程退出后继续跑; - fork 只是点火,记得用
Fiber.join/Fiber.await收尾,才是完整的结构化并发闭环。
掌握这三板斧,Effect v4 的并发模型对你来说就不再是黑盒了 🚀
【免费下载链接】effect-smolCore libraries and experimental work for Effect v4项目地址: https://gitcode.com/GitHub_Trending/ef/effect-smol
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考