Skip to content

Lazy AsyncOption operators

AsyncOption — barrel export.

Re-exports all AsyncOption factories and operators.

Combines a tuple/array of AsyncOptions, preserving heterogeneous types. Returns AsyncOption<None> if any element is None; otherwise AsyncOption<Some<[v0, v1, ...]>>. Like Promise.all but with None short-circuiting.

Two overloads:

  • Tuple overload: preserves per-position heterogeneous types — yields AsyncOption<Some<[number, string]>>.
  • Array overload: runtime-sized homogeneous arrays — yields AsyncOption<Some<T[]>>.
export function all<T extends readonly [AsyncOption<unknown>, ...AsyncOption<unknown>[]]>(
aos: T,
): AsyncOption<
{ [K in keyof T]: T[K] extends AsyncOption<infer V> ? V : never }
>;
export function all<T>(
aos: readonly AsyncOption<T>[],
): AsyncOption<T[]>;

Defined in: async-option/all.ts

import { ofSome, ofNone, all } from '@sandlada/result/async-option';
import type { AsyncOption } from '@sandlada/result';
// Heterogeneous tuple — per-position types preserved.
const a = await all([ofSome(1), ofSome('hi')]).run(); // Some([1, 'hi'])
// Homogeneous array — runtime-sized.
const arr: AsyncOption<number>[] = [ofSome(1), ofSome(2)];
const b = await all(arr).run(); // Some([1, 2])
const c = await all([ofSome(1), ofNone<number>()]).run(); // None

Chains an AsyncOption-returning function on success (monadic bind / flatMap). Supports interoperability by also accepting a function that returns Promise<IOption<U>>. Lazy — returns a new AsyncOption without executing the inner computation.

export function bind<T, U>(
fn: (value: T) => AsyncOption<U> | Promise<IOption<U>>,
): (ao: AsyncOption<T>) => AsyncOption<U>;
export function bind<T, U>(
fn: (value: T) => AsyncOption<U> | Promise<IOption<U>>,
ao: AsyncOption<T>,
): AsyncOption<U>;

Defined in: async-option/bind.ts

import { ofSome } from '@sandlada/result/option';
import { fromOption, bind } from '@sandlada/result/async-option';
const ao = bind((x: number) => fromOption(ofSome(x * 2)), fromOption(ofSome(21)));
const result = await ao.run(); // Some(42)

Returns a Promise<boolean> indicating if the AsyncOption is Some and contains the given value.


export function contains<T>(
value: T,
): (ao: AsyncOption<T>) => Promise<boolean>;
export function contains<T>(
value: T,
ao: AsyncOption<T>,
): Promise<boolean>;

Defined in: async-option/contains.ts

Returns a Promise<boolean> indicating if the AsyncOption is Some and the predicate holds.


export function exists<T>(
predicate: (value: T) => boolean | Promise<boolean>,
): (ao: AsyncOption<T>) => Promise<boolean>;
export function exists<T>(
predicate: (value: T) => boolean | Promise<boolean>,
ao: AsyncOption<T>,
): Promise<boolean>;

Defined in: async-option/exists.ts

Filters the value of an AsyncOption with a predicate. Keeps the Some if the predicate holds; converts to None otherwise. None passes through. Lazy — returns a new AsyncOption without executing the inner computation.

Throw policy: If the predicate throws synchronously or returns a rejected Promise, the error is caught and the result converts to None (canonical catch+convert policy — see AGENTS.md).

export function filter<T>(
predicate: (value: T) => boolean | Promise<boolean>,
): (ao: AsyncOption<T>) => AsyncOption<T>;
export function filter<T>(
predicate: (value: T) => boolean | Promise<boolean>,
ao: AsyncOption<T>,
): AsyncOption<T>;

Defined in: async-option/filter.ts

import { ofSome } from '@sandlada/result/option';
import { fromOption, filter } from '@sandlada/result/async-option';
const ao = filter((x: number) => x > 10, fromOption(ofSome(21)));
const result = await ao.run(); // Some(21)

Flattens a nested AsyncOption.

Single-step only: unwraps exactly one layer. Call flatten repeatedly to flatten deeper nests.


export function flatten<T>(
ao: AsyncOption<AsyncOption<T>>,
): AsyncOption<T>;

Defined in: async-option/flatten.ts

Creates an AsyncOption from a thunk that returns a Promise<IOption>. The thunk is lazy — it won’t execute until .run() is called.

export function from<T>(
thunk: () => Promise<IOption<T>>,
): AsyncOption<T>;

Defined in: async-option/from.ts

import { from } from '@sandlada/result/async-option';
import { ofSome } from '@sandlada/result/option';
const ao = from(() => Promise.resolve(ofSome(42)));
const result = await ao.run(); // Some(42)

Wraps a sync IOption into an AsyncOption (lifts a sync Option into the async world).

export function fromOption<T>(
option: IOption<T>,
): AsyncOption<T>;

Defined in: async-option/fromOption.ts

import { ofSome } from '@sandlada/result/option';
import { fromOption } from '@sandlada/result/async-option';
const ao = fromOption(ofSome(42));
const result = await ao.run(); // Some(42)

Wraps a Promise<T> into an AsyncOption, catching rejections. If the promise resolves, it returns Some(value). If it rejects, it returns None.

The inner Promise is not yet created at construction time; the factory thunk is invoked lazily when .run() is called.

export function fromPromise<T>(
thunk: () => Promise<T>,
): AsyncOption<T>;

Defined in: async-option/fromPromise.ts

import { fromPromise } from '@sandlada/result/async-option';
const ao = fromPromise(() => fetch('/api/data').then(r => r.json()));
const result = await ao.run();

Returns true if the AsyncOption resolves to None. Mirrors the IOption.isNone discriminator as a standalone function.

export function isNone<T>(ao: AsyncOption<T>): Promise<boolean>;

Defined in: async-option/isNone.ts

import { ofSome, ofNone, isNone } from '@sandlada/result/async-option';
await isNone(ofSome(42)); // false
await isNone(ofNone<number>()); // true

Returns true if the AsyncOption resolves to Some. Mirrors the IOption.isSome discriminator as a standalone function.

export function isSome<T>(ao: AsyncOption<T>): Promise<boolean>;

Defined in: async-option/isSome.ts

import { ofSome, ofNone, isSome } from '@sandlada/result/async-option';
await isSome(ofSome(42)); // true
await isSome(ofNone<number>()); // false

Maps the value of an AsyncOption using a synchronous function. Lazy — returns a new AsyncOption without executing the inner computation.

export function map<T, U>(
fn: (value: T) => U,
): (ao: AsyncOption<T>) => AsyncOption<U>;
export function map<T, U>(
fn: (value: T) => U,
ao: AsyncOption<T>,
): AsyncOption<U>;

Defined in: async-option/map.ts

import { ofSome } from '@sandlada/result/option';
import { fromOption, map } from '@sandlada/result/async-option';
const ao = map((x: number) => x * 2, fromOption(ofSome(21)));
const result = await ao.run(); // Some(42)

Maps the value of an AsyncOption using an async function. Lazy — returns a new AsyncOption without executing the inner computation.

export function mapAsync<T, U>(
fn: (value: T) => Promise<U>,
): (ao: AsyncOption<T>) => AsyncOption<U>;
export function mapAsync<T, U>(
fn: (value: T) => Promise<U>,
ao: AsyncOption<T>,
): AsyncOption<U>;

Defined in: async-option/mapAsync.ts

import { ofSome } from '@sandlada/result/option';
import { fromOption, mapAsync } from '@sandlada/result/async-option';
const ao = mapAsync(async (x: number) => x * 2, fromOption(ofSome(21)));
const result = await ao.run(); // Some(42)

Maps the value of an AsyncOption, returning a default on None. The mapper may be sync or async. Throws from the mapper are caught and converted to the default (canonical AsyncOption catch+convert policy).

export function mapOr<T, U>(
defaultValue: U,
fn: (value: T) => U | Promise<U>,
): (ao: AsyncOption<T>) => Promise<U>;
export function mapOr<T, U>(
defaultValue: U,
fn: (value: T) => U | Promise<U>,
ao: AsyncOption<T>,
): Promise<U>;

Defined in: async-option/mapOr.ts

import { ofSome, ofNone, mapOr } from '@sandlada/result/async-option';
const v1 = await mapOr(-1, (x: number) => x * 2, ofSome(21)); // 42
const v2 = await mapOr(-1, (x: number) => x * 2, ofNone<number>()); // -1

Maps the value of an AsyncOption, or computes a default from a thunk on None. Both callbacks may be sync or async.

export function mapOrElse<T, U>(
onNone: () => U | Promise<U>,
fn: (value: T) => U | Promise<U>,
): (ao: AsyncOption<T>) => Promise<U>;
export function mapOrElse<T, U>(
onNone: () => U | Promise<U>,
fn: (value: T) => U | Promise<U>,
ao: AsyncOption<T>,
): Promise<U>;

Defined in: async-option/mapOrElse.ts

import { ofSome, ofNone, mapOrElse } from '@sandlada/result/async-option';
const v1 = await mapOrElse(() => -1, (x: number) => x * 2, ofSome(21)); // 42
const v2 = await mapOrElse(() => -1, (x: number) => x * 2, ofNone<number>()); // -1

Terminal — pattern-matches on both cases of an AsyncOption.

export function match<T, U>(
handlers: { some: (value: T) => U | Promise<U>; none: () => U | Promise<U> },
): (ao: AsyncOption<T>) => Promise<U>;
export function match<T, U>(
handlers: { some: (value: T) => U | Promise<U>; none: () => U | Promise<U> },
ao: AsyncOption<T>,
): Promise<U>;

Defined in: async-option/match.ts

import { ofSome } from '@sandlada/result/option';
import { fromOption, match } from '@sandlada/result/async-option';
const value = await match(
{ some: (v: number) => `success: ${v}`, none: () => 'failure' },
fromOption(ofSome(42))
); // "success: 42"

Creates an AsyncOption that always resolves to None. Equivalent to fromOption(ofNone()) but skips the sync intermediate.

Default T = unknown mirrors option/ofNone so contextual typing flows the same way (const x: AsyncOption<number> = ofNone() widens without needing an explicit generic argument).

export function ofNone<T = unknown>(): AsyncOption<T>;

Defined in: async-option/ofNone.ts

import { ofNone } from '@sandlada/result/async-option';
const ao = ofNone<number>();
const opt = await ao.run(); // None

Lifts a raw value into an AsyncOption<T> that resolves to Some(value). Equivalent to fromOption(ofSome(value)) but skips the sync intermediate.

export function ofSome<T>(value: T): AsyncOption<T>;

Defined in: async-option/ofSome.ts

import { ofSome } from '@sandlada/result/async-option';
const ao = ofSome(42);
const opt = await ao.run(); // Some(42)

Converts an AsyncOption<T> into an AsyncResult<T, E>, supplying an error value for the None case.

export function okOr<T, E>(
error: E,
): (ao: AsyncOption<T>) => AsyncResult<T, E>;
export function okOr<T, E>(
error: E,
ao: AsyncOption<T>,
): AsyncResult<T, E>;

Defined in: async-option/okOr.ts

import { ofSome, ofNone } from '@sandlada/result/async-option';
import { okOr } from '@sandlada/result/async-option';
const r1 = await okOr('missing', ofSome(42)).run(); // Ok(42)
const r2 = await okOr('missing', ofNone<number>()).run(); // Err('missing')

Converts an AsyncOption<T> into an AsyncResult<T, E>, computing the error from a thunk on None (lazy — error is only built when needed).

export function okOrElse<T, E>(
onNone: () => E | Promise<E>,
): (ao: AsyncOption<T>) => AsyncResult<T, E>;
export function okOrElse<T, E>(
onNone: () => E | Promise<E>,
ao: AsyncOption<T>,
): AsyncResult<T, E>;

Defined in: async-option/okOrElse.ts

import { ofSome, ofNone } from '@sandlada/result/async-option';
import { okOrElse } from '@sandlada/result/async-option';
const r1 = await okOrElse(() => 'missing', ofSome(42)).run(); // Ok(42)
const r2 = await okOrElse(() => 'missing', ofNone<number>()).run(); // Err('missing')

Curried form. The inner <T> is deferred so the AsyncOption’s value type is re-inferred at every application site.

Recovers from None by chaining to an alternative AsyncOption or Promise<IOption>. Lazy — returns a new AsyncOption without executing the inner computation.

The fallback’s value type (U) is independent of the input’s value type (T), so a recovery can return a structurally broader or different value — the resulting carrier is AsyncOption<T | U>. Pattern-matching on the result narrows to T on the original Some and to U on the recovered branch.

export function orElse<U>(
fn: () => AsyncOption<U> | Promise<IOption<U>>,
): <T>(ao: AsyncOption<T>) => AsyncOption<T | U>;
export function orElse<T, U>(
fn: () => AsyncOption<U> | Promise<IOption<U>>,
ao: AsyncOption<T>,
): AsyncOption<T | U>;

Defined in: async-option/orElse.ts

import { ofSome, ofNone } from '@sandlada/result/option';
import { fromOption, orElse } from '@sandlada/result/async-option';
const ao = orElse(() => fromOption(ofSome(0)), fromOption(ofNone()));
const result = await ao.run(); // Some(0)
// Cross-type recovery - input `AsyncOption<User>`, fallback `AsyncOption<string>`,
// result `AsyncOption<User | string>`.
type User = { readonly id: string };
const recovered = orElse(
() => fromOption(ofSome('anonymous' as string)),
fromOption(ofNone<User>()),
);

Direct form. T is inferred from the supplied AsyncOption; the result widens to T | U.


Side-effect on the success track of an AsyncOption. Calls fn with the value on Some and passes the original Option through unchanged. Lazy — returns a new AsyncOption without executing the inner computation.

Throw policy: If the side-effect callback throws, the result converts to None (canonical tap/tee policy — see AGENTS.md).

export function tap<T>(
fn: (value: T) => void,
): (ao: AsyncOption<T>) => AsyncOption<T>;
export function tap<T>(
fn: (value: T) => void,
ao: AsyncOption<T>,
): AsyncOption<T>;

Defined in: async-option/tap.ts

import { ofSome } from '@sandlada/result/option';
import { fromOption, tap } from '@sandlada/result/async-option';
const ao = tap((v: number) => console.log(v), fromOption(ofSome(42)));
await ao.run(); // Logs 42, returns Some(42)

Side-effect on the success track of an AsyncOption using an async function. Calls fn with the value on Some and passes the original Option through unchanged. Lazy — returns a new AsyncOption without executing the inner computation.

Throw policy: If the side-effect callback throws (or rejects), the result converts to None (canonical tap/tee policy — see AGENTS.md).

export function tapAsync<T>(
fn: (value: T) => void | Promise<void>,
): (ao: AsyncOption<T>) => AsyncOption<T>;
export function tapAsync<T>(
fn: (value: T) => void | Promise<void>,
ao: AsyncOption<T>,
): AsyncOption<T>;

Defined in: async-option/tapAsync.ts

import { ofSome } from '@sandlada/result/option';
import { fromOption, tapAsync } from '@sandlada/result/async-option';
const ao = tapAsync(async (v: number) => { console.log('saving', v); }, fromOption(ofSome(42)));
await ao.run(); // returns Some(42) after the async side effect

Transposes an AsyncOption<AsyncResult<T, E>> into an AsyncResult<AsyncOption<T>, E>.

  • Some(Ok(v)) → Ok(Some(v))
  • Some(Err(e)) → Err(e)
  • None → Ok(None)
export function transpose<T, E>(
ao: AsyncOption<AsyncResult<T, E>>,
): AsyncResult<AsyncOption<T>, E>;

Defined in: async-option/transpose.ts

import { ofSome } from '@sandlada/result/async-option';
import { fromResult } from '@sandlada/result/async-result';
import { ok } from '@sandlada/result/factories';
import { transpose } from '@sandlada/result/async-option';
const r = await transpose(ofSome(fromResult(ok(42)))).run();
// r.isSuccess === true; r.value is an AsyncOption resolving to Some(42)

Extracts the value from an AsyncOption, or throws if None. Use sparingly — prefer unwrapOr, unwrapOrElse, or match in most code.

export function unwrap<T>(ao: AsyncOption<T>): Promise<T>;

Defined in: async-option/unwrap.ts

import { ofSome, ofNone, unwrap } from '@sandlada/result/async-option';
const v = await unwrap(ofSome(42)); // 42
await unwrap(ofNone()); // throws Error

Extracts the value from an AsyncOption, or returns a default value.

export function unwrapOr<T>(
defaultValue: T | Promise<T>,
): (ao: AsyncOption<T>) => Promise<T>;
export function unwrapOr<T>(
defaultValue: T | Promise<T>,
ao: AsyncOption<T>,
): Promise<T>;

Defined in: async-option/unwrapOr.ts

import { ofSome, ofNone } from '@sandlada/result/option';
import { fromOption, unwrapOr } from '@sandlada/result/async-option';
const v1 = await unwrapOr(0, fromOption(ofSome(42))); // 42
const v2 = await unwrapOr(0, fromOption(ofNone())); // 0

Extracts the value from an AsyncOption, or computes a default from a thunk on None. Lazy — the default is only computed when needed.

export function unwrapOrElse<T>(
onNone: () => T | Promise<T>,
): (ao: AsyncOption<T>) => Promise<T>;
export function unwrapOrElse<T>(
onNone: () => T | Promise<T>,
ao: AsyncOption<T>,
): Promise<T>;

Defined in: async-option/unwrapOrElse.ts

import { ofSome, ofNone, unwrapOrElse } from '@sandlada/result/async-option';
const v1 = await unwrapOrElse(() => 0, ofSome(42)); // 42
const v2 = await unwrapOrElse(() => 0, ofNone<number>()); // 0

Combines N AsyncOptions (N ≥ 2) with a function. If all resolve to Some, returns AsyncOption<Some(fn(a, b, ...))>. If any resolves to None, returns AsyncOption<None>. Async rejections from the callback propagate (they are not caught).

The arity of fn fixes the number of AsyncOptions accepted — variadic via tuple inference. The catch-all mapped-type variadic handles every arity ≥ 2 in a single pair of overloads.

Design note (type safety): This file intentionally does NOT declare per-arity overloads. TypeScript’s function-type bivariance would let a 2-argument fn match a 3-argument overload (with the third parameter silently ignored), and a 0-argument fn match a 2-argument curried form. The single variadic below avoids both holes: T is inferred from fn’s actual parameter list, and the constraint readonly [unknown, unknown, ...unknown[]] requires ≥ 2 elements.

export function zipWith<T extends readonly [unknown, unknown, ...unknown[]], R>(
fn: (...args: T) => R | Promise<R>,
): (...aos: { [K in keyof T]: AsyncOption<T[K]> }) => AsyncOption<R>;
export function zipWith<T extends readonly [unknown, unknown, ...unknown[]], R>(
fn: (...args: T) => R | Promise<R>,
...aos: { [K in keyof T]: AsyncOption<T[K]> }
): AsyncOption<R>;

Defined in: async-option/zipWith.ts

import { zipWith, ofSome, ofNone } from '@sandlada/result/async-option';
// Arity 2
const r1 = await zipWith((a: number, b: number) => a + b, ofSome(1), ofSome(2)).run();
// Some(3)
// Arity 5
const r2 = await zipWith(
(a: number, b: number, c: number, d: number, e: number) => a + b + c + d + e,
ofSome(1), ofSome(2), ofSome(3), ofSome(4), ofSome(5),
).run();
// Some(15)