Effect.runSync 为什么会抛 AsyncFiberError?

一个 Effect<number> 可能是立刻算出来的,也可能等了一个网络请求,类型上看不出区别。于是对着包着 Promise 的 Effect 调用 Effect.runSync,抛出的是 AsyncFiberError。

答案是:Effect.runSync 无法运行异步的 Effect,这是个运行时错误,而不是类型错误。 所以需要同步运行的入口要自己保证内部没有异步;绝大多数情况直接用 runPromise 就不会碰到。

这是 Effect 的一个设计取舍:类型不区分同步和异步,换来的是改实现时调用者无感,代价是这个运行时才暴露的错误。想了解 Effect 怎么包装外部代码,见 怎么把已有的同步、Promise 代码包装成 Effect?

Effect 的类型为什么不区分同步和异步?

Effect.sync 与 Effect.promise 的结果类型都是 Effect<A>。Effect 的类型不区分同步和异步。 签名都是 Effect<A, E, R>,调用者通过 yield* 使用,对内部是同步还是异步没有感知,yield* 的用法见 第一个 Effect 程序。

这个设计有什么好处?

你可以在不改变调用者的前提下,把同步实现换成异步实现。想象一个 lookup 函数最初从内存 Map 读取,后来改成查数据库。

用 Promise 的话,所有同步调用者都要加上 await、签名改成 async,这个「颜色」会沿着调用链一路传染。Effect 里不会。

代价是什么,怎么避开?

代价就是开头那个错误:Effect.runSync 对包着 Promise 的 Effect 会抛出 AsyncFiberError。因为它是运行时错误,编译器不会提醒你。

所以需要同步运行的入口,要自己保证内部没有异步。其他情况直接用 runPromise,就不会碰到这个问题。

本文依据 effect 4.0.5 的行为整理。示例与结论没有在本地逐条运行验证,AsyncFiberError 的触发条件以所用版本为准。