Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,19 @@ const task = yield * op; // returns a TASK (Future) and starts it
- `all()` accepts an array of operations and evaluates them concurrently.
- It returns an array of results in input order.
- If any member errors, `all()` errors and halts the other members.
- If you need "all operations either complete or error" (no fail-fast), wrap
each member to return a railway-style result (e.g. `{ ok: true, value }` /
`{ ok: false, error }`) instead of letting errors escape.
- If you need all results regardless of success or failure, use `allSettled()`
instead of wrapping each member in railway-style results.

## `allSettled()`

**Rules**

- `allSettled()` accepts an array of operations and evaluates them concurrently.
- It returns an array of `Result<T>` objects in input order
(`{ ok: true, value }` or `{ ok: false, error }`).
- It never short-circuits on error — all operations run to completion.
- It is analogous to `Promise.allSettled()`, but uses Effection's `Result<T>`
shape.

## `call()`

Expand Down
38 changes: 38 additions & 0 deletions docs/async-rosetta-stone.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ counterparts is reflected in the “Async Rosetta Stone.”
| `Promise` | `Operation` |
| `new Promise()` | `action()` |
| `Promise.withResolvers()` | `withResolvers()` |
| `Promise.allSettled()` | `allSettled()` |
| `for await` | `for yield* each` |
| `AsyncIterable` | `Stream` |
| `AsyncIterator` | `Subscription` |
Expand Down Expand Up @@ -201,6 +202,38 @@ function* main() {
};
```

## `Promise.allSettled()` \<=> `allSettled()`

Wait for all operations to settle regardless of whether they succeed or fail.
Unlike [`all()`][all], [`allSettled()`][allSettled] never short-circuits —
every result is represented as either `{ ok: true, value }` or
`{ ok: false, error }`.

Wait for all promises to settle with `Promise.allSettled()`:

```js
let [user, comments] = await Promise.allSettled([
fetchUser(id),
fetchComments(id),
]);
```

Wait for all operations to settle with `allSettled()`:

```js
import { allSettled } from 'effection';

let [user, comments] = yield* allSettled([
fetchUser(id),
fetchComments(id),
]);
```

The shape of the results is slightly different from `Promise.allSettled()`.
Effection uses its [`Result<T>`][result] type so that settled results compose
the same way they do elsewhere in the library. You can construct these values
with [`Ok()`][ok] and [`Err()`][err].

## `for await` \<=> `for yield* each`

Loop over an AsyncIterable with `for await`:
Expand Down Expand Up @@ -284,6 +317,11 @@ let subscription = subscribe(asyncIterator);
```

[call]: /api/v4/call
[all]: /api/v4/all
[allSettled]: /api/v4/allSettled
[result]: /api/v4/Result
[ok]: /api/v4/Ok
[err]: /api/v4/Err
[until]: /api/v4/until
[run]: /api/v4/run
[scope-run]: /api/v4/Scope#interface_Scope-methods
Expand Down
15 changes: 15 additions & 0 deletions docs/spawn.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,19 @@ main(function *() {
});
```

If you need to wait for all operations to settle regardless of whether they
succeed or fail, use [`allSettled()`][allSettled] instead. Unlike `all()`,
`allSettled()` never short-circuits on error — each result is represented as
[`Result<T>`][result]: `{ ok: true, value }` or `{ ok: false, error }`.

``` javascript
import { allSettled, main } from 'effection';

main(function *() {
let [dayUS, daySweden] = yield* allSettled([fetchWeekDay('est'), fetchWeekDay('cet')]);
});
```

## Spawning in a Scope

The `spawn()` operation always runs its operation as a child of the current
Expand Down Expand Up @@ -207,4 +220,6 @@ You can learn more about this in the [scope guide](./scope).
[sc-for-js]: /blog/2026-02-06-structured-concurrency-for-javascript
[express]: https://expressjs.org
[scope.run]: /api/v4/Scope
[allSettled]: /api/v4/allSettled
[result]: /api/v4/Result
[Operation]: /api/v4/Operation
53 changes: 53 additions & 0 deletions lib/all-settled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { all } from "./all.ts";
import { box } from "./box.ts";
import type { Result } from "./result.ts";
import type { Operation, Yielded } from "./types.ts";

/**
* Block and wait for all of the given operations to settle. Returns
* an array of results indicating whether each operation succeeded or
* errored. This has the same purpose as
* [Promise.allSettled](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled).
*
* Unlike {@link all}, `allSettled` never rejects — every operation
* result is represented as either `{ ok: true, value }` or
* `{ ok: false, error }`.
*
* ### Example
*
* ``` javascript
* import { allSettled, expect, main } from 'effection';
*
* await main(function*() {
* let [google, notASite] = yield* allSettled([
* expect(fetch('http://google.com')),
* expect(fetch('http://nope.example')),
* ]);
* // google => { ok: true, value: Response }
* // notASite => { ok: false, error: Error }
* });
* ```
*
* @param ops a list of operations to wait for
* @returns the list of settled results, in the order they were given
* @since 4.1
*/
export function* allSettled<T extends readonly Operation<unknown>[] | []>(
ops: T,
): Operation<AllSettled<T>> {
let results = yield* all(
ops.map((operation) => box(() => operation)) as {
[P in keyof T]: Operation<Result<Yielded<T[P]>>>;
},
);
return results as AllSettled<T>;
}

/**
* This type allows you to infer heterogenous operation types.
* e.g. `allSettled([sleep(0), expect(fetch("https://google.com"))])`
* will have a type of `Operation<[Result<void>, Result<Request>]>`
*/
type AllSettled<T extends readonly Operation<unknown>[] | []> = {
-readonly [P in keyof T]: Result<Yielded<T[P]>>;
};
1 change: 1 addition & 0 deletions lib/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export * from "./resource.ts";
export * from "./call.ts";
export * from "./race.ts";
export * from "./all.ts";
export * from "./all-settled.ts";
export * from "./lift.ts";
export * from "./queue.ts";
export * from "./signal.ts";
Expand Down
36 changes: 33 additions & 3 deletions lib/result.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
/**
* @ignore
* A value representing either a successful outcome or an error.
*
* `Result<T>` is used in APIs when you want to preserve both successes and
* failures instead of short-circuiting on the first error.
*
* A successful result has the shape `{ ok: true, value }` and a failed result
* has the shape `{ ok: false, error }`.
*
* @since 4.1
*/
export type Result<T> = {
readonly ok: true;
Expand All @@ -10,7 +18,18 @@ export type Result<T> = {
};

/**
* @ignore
* Construct a successful {@link Result}.
*
* ### Example
*
* ```javascript
* import { Ok } from 'effection';
*
* let result = Ok("hello");
* // { ok: true, value: "hello" }
* ```
*
* @since 4.1
*/
export function Ok(): Result<void>;
export function Ok<T>(value: T): Result<T>;
Expand All @@ -22,7 +41,18 @@ export function Ok<T>(value?: T): Result<T | undefined> {
}

/**
* @ignore
* Construct a failed {@link Result}.
*
* ### Example
*
* ```javascript
* import { Err } from 'effection';
*
* let result = Err(new Error("oh no"));
* // { ok: false, error: Error("oh no") }
* ```
*
* @since 4.1
*/
export const Err = <T>(error: Error): Result<T> => ({ ok: false, error });

Expand Down
Loading