Effect 里中断和失败有什么区别?

用户关掉页面,页面上还在跑的请求被取消了。这时候日志里要不要出现一条错误?要不要自动重试?

不要。在 Effect 里,中断(interrupt)不是失败,也不是缺陷,而是第三种结局:它表示「有人不再需要这个结果了」,不该被当成错误去重试或上报。 一个被中断的 fiber,它的 Exit 里带有中断信息,不会混进 catch 能捕获的失败里。

这个区分靠 Effect 的类型和结局模型保证。下面先交代失败、缺陷各是什么,再看中断在 Exit 里怎么表示、什么时候才会生效,以及被中断时清理逻辑怎么办。

失败、缺陷、中断分别是什么?

Effect 的结局有三类。失败是 E 类型里的可预期错误,属于业务上会出错的情形,类型系统会提醒你处理,详见 Effect 里错误怎么变成类型。缺陷是类型之外的意外,相当于 bug。中断是第三种:既不是业务出错,也不是 bug。

前两类是「出事了」,中断是「不用做了」。用户离开页面、父任务已经结束,都属于这一类。

怎么判断一个 fiber 是被中断的?

fiber 的结局被包成 Exit 值。用 Fiber.await 等一个 fiber,它不会重新抛出失败,而是把 Exit 交给你;被中断的 fiber,Exit 带有中断的信息,可以用 Exit.hasInterrupts 判断。

与之对比,Fiber.join 会在子 fiber 失败时把同样的失败重新抛出。需要区分「失败」和「被中断」时,用 Fiber.await 拿到 Exit 再判断,比用 join 更直接。

被中断后为什么不该重试或上报?

因为中断是调用方主动的决定,不是故障。在 catch 里重试被中断的任务,或者把取消当错误打日志,都是概念混淆:用户关掉页面不是系统出了毛病。

中断在 Exit 里有自己的表示,不会混进 catch 能捕获的失败里,所以只要不去手动把它翻译成失败,这个混淆本来就很难发生。

为什么有的任务打不断?

fiber 不是线程,运行时不会强行「杀」它。中断是协作式的:生效点是 fiber 让出控制的地方,比如 sleep、等待 I/O,以及 yield* 之间的调度点。一段纯同步的死循环,没有让出点,就没法被打断,这和 JavaScript 本身的单线程特性一致。

好消息是,正常写出来的 Effect 程序里到处都是让出点,所以中断通常很及时。

中断时清理逻辑怎么办?

Fiber.interrupt 会等待目标 fiber 完成清理再返回,不是「发完信号就走」。清理逻辑是中断语义的一部分,具体怎么写,见 Effect 的资源与 Scope。

中断的由来,也就是子 fiber 为什么会被父 fiber 自动中断,见 Effect 的 fiber 是什么,为什么子任务归父任务管?。

本文整理自 Effect 教程中关于 fiber 的章节,没有再亲自运行过相关代码,也没有核对具体的 effect 版本号。Exit.hasInterrupts 等 API 名称取自教程原文,使用前请以你所用版本的文档为准。