Effect.run 应该放在哪里?
在业务函数里到处写 await Effect.runPromise(...),是把 Effect 当成更啰嗦的 Promise 用,最常见的用歪方式。
运行 Effect 的那一步只应该出现在程序最外层的入口(也就是 Effect.run* 这一族函数):main 函数、HTTP 框架的 handler 适配处、测试的运行器处。业务逻辑内部只构造、组合 Effect,不运行它。
Effect 是对计算的描述,类型是 Effect<A, E, R>,写出来之后什么都还没有发生,细节见 为什么我用 Effect。运行是把描述变成真实执行的那一步,下面讲为什么它该留在边缘,以及有哪些运行器。
为什么不能在中间运行?
一旦在中间运行,你就把一个本该由外层统一决定的事情提前做了:
- 依赖需要在中途就提供完,外层再也没有机会替换(比如测试里换成假实现)
- 取消和资源释放被切断,内层的 fiber 脱离了外层的管理
- 失败被提前压扁成 Promise 的 rejection,类型信息丢失
判断标准:如果一个函数既返回 Promise 又在内部运行 Effect,它的 E 和 R 就都丢了。
可以用电源插座来类比:业务代码是电器,只管插上;通电只在配电箱那一处发生。如果每个电器内部自带一个发电机,就没法统一断电和限流了。
入口处怎么写?
async function main() {
const value = await Effect.runPromise(program)
console.log(value)
}常用的运行器怎么选?
Effect.runPromise:最常用,异步入口。失败会变成 rejection。Effect.runPromiseExit:不抛异常,返回Exit,适合需要自己处理成功和失败的边界。Effect.runSync:同步入口,要求整个 Effect 是同步的。Effect.runFork:后台运行,返回 fiber。长期运行的服务入口常用它。
怎么接上已有的取消机制?
runPromise、runPromiseExit、runFork 接受可选的 { signal },可以把外部的 AbortSignal 接到 Effect 的中断上。runSync 没有这个选项。
边界与测试的更多做法,见 运行时边界与测试。
本文的 API 与行为描述全部取自教程原章节,代码没有在写作时重新运行。原章节只提到 Effect v4,没有给出具体版本号,写法以你安装的版本为准。