Effect TS · Hands-on 教程

把"异步、错误、依赖"写进类型的程序值

基于 effect 3.21(单一 effect 包,yield* 无适配器写法)· 全程约半天 · 代码在 Node 24 + TS 5.5 下校验 · 面向 TS 熟练、函数式较新、想在真实后端/Agent 落地的工程师。

这份教程的形状

  • 核心心智模型:Effect<A, E, R> 三通道、类型化错误、Context/Layer 依赖注入、Runtime
  • 并发:Fiber、结构化并发、有界并发、race/timeout、中断与资源安全
  • 7 章 hands-on:概念 → 原理 → 三阶代码实操 → 陷阱 → 综合项目 → 自测

①适合谁

三条前置能力,缺一条都会读得吃力:

  • 能用 TypeScript 写过后端或全栈代码,读得懂泛型、联合类型、async/await——不是第一次见 TS。
  • 用过 Promise.all / try-catch / AbortController,知道它们在复杂并发和错误传播下哪里别扭。
  • 目标是在真实 TS 后端或 AI Agent 里引入更强的错误处理、依赖注入、并发控制——带着要落地的问题来读。

②不适合谁(去别处更快)

如果你是更合适的起点
完全没写过 TypeScript先读同目录的 TypeScript 教程,再回来
只想要一个 Result 类型,不碰整套体系neverthrow 更轻、可逐函数引入
想学 Schema / Stream / RPC 全家桶本教程聚焦 core + 并发;末尾「学完之后」给延伸入口

③读完之后你能做到什么

读完你会用一种新的眼光看待异步:一个操作的成功类型、失败方式、依赖项本该全部写进类型签名,而 async/await + try-catch 只暴露了第一个、藏起了后两个。一旦失败和依赖也变成编译器能看见的东西,重构、并发和取消就从"靠自觉小心"变成"编译器替你盯着"。具体地,你能:

  • 看着 Effect<A, E, R> 签名读懂任意一段 Effect 代码:它产出什么、会怎么失败、依赖谁。
  • 把一个会 throw 的 Promise 函数,改写成"错误进入类型、可被 catchTag 穷尽处理"的 Effect。
  • 用 Context.Tag + Layer 把依赖从业务代码里抽出来,在测试与生产间换实现而不改调用处。
  • 用 Effect.all({ concurrency }) / race / timeout 写出有界并发、且失败时自动取消并清理资源的调度逻辑。
  • 判断一段代码该用 Effect 还是直接用 Promise,并说出理由。

一句话本质

Effect 不是"执行一个异步操作",而是把"一个产出 A、失败类型为 E、运行依赖 R 的程序"写成一个惰性、不可变、类型完全可见的值。它在你交给 runtime 之前什么都不做——async/await + try-catch 把成功、失败、依赖三件事藏在运行时,Effect 把它们提到类型签名 Effect<A, E, R> 里。

现状速览 · 截至 2026-06

稳定核心:effect v3.x(约 3.21),单一包;API 自 3.0(2024-04)起承诺语义化版本稳定。直接 yield* effect——旧的生成器适配器 yield* _(effect) 已在 2024-04 移除,需 TS 5.5+。本教程教的就是这套。

变动中:effect v4 Beta 于 2026-02 发布(重写 fiber runtime、包体 ~70kB→~20kB、新增 effect/unstable/* 命名空间)。但 v3 仍是生产推荐;正文会在关键处标出 v4 走向。

已淘汰:fp-ts(已并入 Effect 生态,仅维护,其作者加入 Effect 团队);旧多包导入 @effect/io / @effect/data;yield* _(...) 适配器。学到这些旧写法说明资料早于 2024-04。

先承认一件事 · 流畅感不是学会

Effect 读起来会很"顺",因为每个 API 名字都直白。但顺 ≠ 学会。读这份教程时,盯住三种自欺信号:

「我读得很顺」——往往只是熟悉了名字,没建立"为什么是这个设计"的结构。
「我做题很快」——多半在套例题模板,没真正做判别。
「我没卡壳」——多半根本没触到难点。本教程每章末有"刚好够不着"的挑战,专门制造卡壳。卡住是对的。

④概念地图

把后面所有细节挂在这张图上。中心是一个值,三个类型参数各自牵出一套独立机制——这正是和 Promise 最不一样的地方。

Effect<A, E, R> 惰性程序蓝图 运行 → A Runtime / Fiber run* 才真正执行 失败 E 错误模型 Cause · Exit · catchTag 依赖 R 依赖注入 Context · Layer 并发 结构化并发 fork · race · 中断
图 0一个 Effect 值,四条出边。注意:三个类型参数 A/E/R 不是装饰——每个都对应一套独立机制,而 async/await 只暴露了 A,把 E 和 R 藏了起来。

⑤学习路径

顶部那条 00 — 01 — 02 … 面包屑就是路径,每章会高亮你所在的位置。按目标选切入点:

⑥目录

⑦学完之后(下一步)

  • Schema——把 unknown 解析成类型安全的数据,错误进入 E 通道。给你的 schema 加上"边界处的运行时校验"。
  • Stream——把单值 Effect 扩展成可背压、可中断、资源安全的多值流。给 schema 加上"随时间推进的多个值"。
  • @effect/platform——跨运行时的 HTTP/文件/进程抽象。给 schema 加上"与外部世界交互的标准接口"。
  • 可观测性——内建的结构化日志、Span、Metrics。给 schema 加上"生产环境能看见 fiber 在做什么"。
  • v4 迁移——等 v4 稳定后,对照官方 v3→v4 映射表。给 schema 加上"运行时重写后哪些 API 变了"。