Skip to content
LogoLogo

VIP-18: running Effects concurrently: try.all, try.race, try.any, try.allSettled

Status: accepted by Will, 2026-10-28.

Decision

const [u, o] = try.all [fetchUser(id), fetchOrders(id)]  // yield* Effect.all([…], { concurrency: "unbounded" })
const fast   = try.race [primary(), mirror()]            // yield* Effect.raceAllFirst([…])   first to finish
const ok     = try.any [primary(), mirror()]             // yield* Effect.raceAll([…])        first to succeed
const rs     = try.allSettled [a(), b()]                 // yield* Effect.all([…].map(Effect.exit), { concurrency: "unbounded" })
  • These are TC39's await operations, run with try (VIP-5) instead of await. Each is exact sugar for an Effect function.

  • The names keep JavaScript's meanings: try.all runs everything at once, like Promise.all, even though Effect.all defaults to one at a time. try.race gives the first to finish, and try.any the first to succeed.

  • Amended 2026-10-29: try.allSettled gives each element's Exit (success, failure, defect or interruption), and the whole never fails, as Promise.allSettled never rejects. Recorded under Will's standing approval of recommendations.

  • They use the same keyword.name shape as VIP-17's fork.in.

  • Amended 2026-10-29: operands are arrays. An object of Effects uses the all global: try all({ u: fetchUser(id), o: fetchOrders(id) }, { concurrency: "unbounded" }). An object form of try.all can be added later without breaking anything. Recorded under Will's standing approval of recommendations.

  • Amended 2026-10-29: the try.allSettled counterpart for an array literal. The comment above writes […].map(Effect.exit), which is a TypeScript error when the elements differ in type. For an array literal the counterpart wraps each element, Effect.all([Effect.exit(a()), Effect.exit(b())], { concurrency: "unbounded" }), which gives the same Exit and keeps the tuple type; any other operand uses xs.map(Effect.exit). Erratum to the comment, following the TypeScript counterpart (Will: VibeLang mirrors the TypeScript counterpart where a VIP is silent).

  • Ambiguity: none. try is a reserved word, so try. can't start anything else.

  • Amended 2026-10-30: shared static rules. try.all, try.race, try.any and try.allSettled all run Effects under VIP-5: outside effect code they receive EK0501. Each takes an array of Effects under the operand rule; an invalid operand receives EK1801. These are the existing context and operand checks for the complete family, not new accepted contexts or operand forms.

  • Amended 2026-10-31: spreads in a try.allSettled literal. A literal containing a spread is evaluated once, in native element order, with each spread using its value's own iterator; the counterpart wraps that array by index (source.map(Effect.exit)) and adds no iteration of a compiler-created array, so a changed Array.prototype[Symbol.iterator] sees exactly the calls native [a(), ...xs, b()] makes. Literal tuple typing, spread-as-array typing and the non-literal heterogeneous-tuple rule are unchanged. Decided by the agent on 2026-10-31 from the exact-sugar contract (decided detail 252; loop round 5 OPUS-3 and ASTRA-2).

History

About 37 findings over rounds 2 to 11, about when elements are evaluated and about do elements. VIP-1, VIP-2 and VIP-5 removed both, and none apply to this design.

Prior art

Promise.all, race, any and allSettled; TC39's await-operations proposal.

Will's words

"this is a yes"