Effect.all 里一个任务失败,其余任务会怎样?

用 Promise.all 并发发请求,其中一个 reject,整体立刻 reject,可另外那些已经发出去的请求并没有被取消,它们还在后台继续跑,只是结果没人要了。

在 Effect 里,并发模式下的 Effect.all 和 Effect.forEach,任何一个任务失败,整个组合子立即以该失败结束,其余仍在运行的任务会被中断,清理逻辑会跑。 想要每个任务各自独立,就用 Effect.result 把失败变成数据。

这个行为有保证,是因为结构化并发:组合子内部为每个任务开一个子 fiber,组合子本身是父 fiber,父结束时子被中断。如果你对 fiber 还不熟,先看 Fiber 与结构化并发。并发度怎么设,见 Effect.all 和 Effect.forEach 怎么控制并发度?。

默认会发生什么?

两件事:

  1. 任何一个任务失败,整个组合子立即以该失败结束。
  2. 其余仍在运行的任务会被中断,它们的清理逻辑会跑,而不是被放任不管。

「一个失败,其余一起停」是默认且有保证的行为。如果批处理是「全有或全无」的事务性操作,这正是你要的。

想让每个任务独立,最后告诉我谁成谁败怎么办?

思路是把失败变成数据:Effect.result 把一个 Effect 的失败具象化成 Result 值(Success 或 Failure),自己不再失败。Effect.forEach 里给每个元素套一层它;Effect.all 则直接支持 mode: "result"。

import { Effect } from "effect"

declare const syncOne: (id: string) => Effect.Effect<void, Error>
declare const ids: ReadonlyArray<string>

// forEach 没有 mode 选项:每个元素先 Effect.result,失败变成值
const syncAll = Effect.forEach(ids, (id) => Effect.result(syncOne(id)), {
  concurrency: 5
})
// 结果数组的每一项是 Result,不会因为单个失败而中止其余任务

// all 可以直接用 mode: "result",输出形状不变,每一项变成 Result
const pair = Effect.all([syncOne("a"), syncOne("b")], {
  concurrency: 2,
  mode: "result"
})

该选哪一种?

要想清楚的是:批处理里,一项失败要不要拖垮整批?全有或全无的事务性操作,用默认语义;互相独立的批量任务,用 Effect.result(或 Effect.all 的 mode: "result"),然后在最后统一汇总失败。「谁来处理错误」的整体思路见 错误设计。

能不能在任务里 catch 掉错误来避免中断别人?

别这么做。在任务内部吞掉错误再返回占位值,会丢失信息。用 Effect.result 把失败保留为数据,更诚实,汇总时也更容易处理。

这篇的行为描述来自对 Effect 并发组合子语义的梳理,原文没有标注 effect 版本号,也没有记录本地运行结果,所以这里的结论和代码都没有在本地实际运行验证。API 名和选项以你所用版本的官方文档为准。