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
1 change: 1 addition & 0 deletions bench/package.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"name": "effection-benchmarks",
"type": "module",
"private": true,
"dependencies": {
Expand Down
3 changes: 2 additions & 1 deletion docs/agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,8 @@ const task = yield * op; // returns a TASK (Future) and starts it

- `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 }`).
(`{ ok: true, value }` or `{ ok: false, error }`). Failed results also expose
`cause` as an alias for the deprecated `error` field.
- It never short-circuits on error — all operations run to completion.
- It is analogous to `Promise.allSettled()`, but uses Effection's `Result<T>`
shape.
Expand Down
3 changes: 2 additions & 1 deletion docs/async-rosetta-stone.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,8 @@ function* main() {
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 }`.
or `{ ok: false, error }`. Failed results also expose `cause` as an alias for the
deprecated `error` field.

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

Expand Down
3 changes: 2 additions & 1 deletion docs/spawn.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,8 @@ 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 }`.
[`Result<T>`][result]: `{ ok: true, value }` or `{ ok: false, error }`. Failed
results also expose `cause` as an alias for the deprecated `error` field.

``` javascript
import { allSettled, main } from 'effection';
Expand Down
3 changes: 2 additions & 1 deletion lib/all-settled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import type { Operation, Yielded } from "./types.ts";
*
* Unlike {@link all}, `allSettled` never rejects — every operation
* result is represented as either `{ ok: true, value }` or
* `{ ok: false, error }`.
* `{ ok: false, error }`. Failed results also expose `cause` as an alias for
* the deprecated `error` field.
*
* ### Example
*
Expand Down
19 changes: 16 additions & 3 deletions lib/result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
* automatically percolate.
*
* A successful result has the shape `{ ok: true, value }` and a failed result
* has the shape `{ ok: false, error }`.
* has the shape `{ ok: false, error }`. Failed results created by Effection also
* expose `cause` as an alias for `error`.
*
* @since 4.1
*/
Expand All @@ -15,6 +16,11 @@ export type Result<T> = {
value: T;
} | {
readonly ok: false;
/**
* Read-only alias for `error`. Optional to accept existing hand-written results.
*/
readonly cause?: Error;
/** @deprecated Use `cause` instead. */
error: Error;
};

Expand Down Expand Up @@ -42,7 +48,8 @@ export function Ok<T>(value?: T): Result<T | undefined> {
}

/**
* Construct a failed {@link Result}.
* Construct a failed {@link Result} with `cause` and `error` referring to the
* same Error. The `cause` alias is non-enumerable to preserve the v4 object shape.
*
* ### Example
*
Expand All @@ -56,12 +63,18 @@ export function Ok<T>(value?: T): Result<T | undefined> {
* @since 4.1
*/
export function Err<T>(cause: unknown): Result<T> {
return {
let result: Result<T> = {
ok: false,
error: cause instanceof Error
? cause
: new ThrownValueError(String(cause), { cause }),
};
return Object.defineProperty(result, "cause", {
configurable: true,
get() {
return result.error;
},
});
}

class ThrownValueError extends Error {
Expand Down
32 changes: 32 additions & 0 deletions test/all-settled.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,38 @@ describe("allSettled()", () => {
expect(result.length).toEqual(3);
});

it("exposes cause as an alias for error in failed results", async () => {
let causes = [
new Error("boom"),
{ message: "boom" },
"boom",
null,
undefined,
];
let results = await run(() =>
allSettled(causes.map((cause) =>
call(function* () {
yield* sleep(0);
throw cause;
})
))
);

for (let [index, result] of results.entries()) {
if (result.ok) {
throw new Error("expected a failed result");
}
expect(result.cause).toBe(result.error);
expect(result.cause).toBeInstanceOf(Error);
let cause = causes[index];
if (cause instanceof Error) {
expect(result.cause).toBe(cause);
} else {
expect(result.cause?.cause).toBe(cause);
}
}
});

it("does not halt sibling operations when one fails", async () => {
let teardown = false;
await run(function* () {
Expand Down
41 changes: 39 additions & 2 deletions test/result.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, it } from "./suite.ts";
import { describe, expect, expectType, it } from "./suite.ts";

import { Err, Ok, unbox } from "../mod.ts";
import { Err, Ok, type Result, unbox } from "../mod.ts";

describe("Result", () => {
it("constructs successful results with Ok()", () => {
Expand All @@ -24,6 +24,43 @@ describe("Result", () => {
expect(result.error.name).toEqual("ThrownValueError");
expect(result.error.message).toEqual("oh no");
expect(result.error.cause).toEqual("oh no");
expect(result.cause).toBe(result.error);
});

it("aliases cause to error without changing the v4 object shape", () => {
let error = new Error("oh no");
let result = Err(error);

if (result.ok) {
throw new Error("expected a failed result");
}

expect(result.cause).toBe(error);
expect({ ...result }).toEqual({ ok: false, error });
expectType<Error>(result.error);
expectType<Error | undefined>(result.cause);
});

it("reflects updates to error through cause", () => {
let result = Err(new Error("original"));

if (result.ok) {
throw new Error("expected a failed result");
}

let error = new Error("changed via error");
result.error = error;
expect(result.cause).toBe(error);

expect(() => unbox(result)).toThrow(error);
});

it("accepts existing hand-written results without cause", () => {
let error = new Error("oh no");
let result: Result<string> = { ok: false, error };

expect(result.cause).toBeUndefined();
expect(() => unbox(result)).toThrow(error);
});

it("unboxes non-Error causes", () => {
Expand Down
Loading