Skip to content

Promise Option operators

Eager operators on Promise of Option — barrel export.

Mirrors the structure of promise-result/ but for the Option type. For lifting sync Option into Promise of Option, see ../bridge-option/index.js.

Chains an async option-returning function. fn can return IOption or Promise<IOption>.

Throw policy: if fn throws synchronously or its returned Promise rejects, the result is None. The thrown reason is discarded.

export function bindAsync<T, U>(
f: (a: T) => IOption<U> | Promise<IOption<U>>,
): (r: Promise<IOption<T>>) => Promise<IOption<U>>;
export function bindAsync<T, U>(
f: (a: T) => IOption<U> | Promise<IOption<U>>,
r: Promise<IOption<T>>,
): Promise<IOption<U>>;

Defined in: promise-option/bindAsync.ts

import { bindAsync, unwrapOrAsync } from '@sandlada/result/promise-option';
import { ofSome, ofNone } from '@sandlada/result/option';
const input = Promise.resolve(ofSome(21));
const chained = bindAsync(
(x: number) => (x > 0 ? Promise.resolve(ofSome(x * 2)) : Promise.resolve(ofNone())),
input,
);
const value = await unwrapOrAsync(0, chained);
// value === 42
const failed = Promise.resolve(ofNone<number>());
const fallback = await unwrapOrAsync(
0,
bindAsync((x: number) => Promise.resolve(ofSome(x)), failed),
);
// fallback === 0; bindAsync short-circuits on None

Returns true if the Promise<IOption> is Some and contains the given value.

export function containsAsync<T>(
value: T,
): (r: Promise<IOption<T>>) => Promise<boolean>;
export function containsAsync<T>(
value: T,
r: Promise<IOption<T>>,
): Promise<boolean>;

Defined in: promise-option/containsAsync.ts

import { containsAsync, orElseAsync } from '@sandlada/result/promise-option';
import { ofSome, ofNone } from '@sandlada/result/option';
const input = Promise.resolve(ofSome(42));
const hasFortyTwo = await containsAsync(42, input);
const backup = await orElseAsync(
() => Promise.resolve(ofSome(0)),
Promise.resolve(ofNone<number>()),
);
// hasFortyTwo === true
// backup is Some(0); orElseAsync runs its fallback on None
const empty = Promise.resolve(ofNone<number>());
const missing = await containsAsync(42, empty);
// missing === false; containsAsync short-circuits on None

Returns true if the Promise<IOption> is Some and the predicate holds. Returns false on None or when the predicate does not hold.

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

export function existsAsync<T>(
predicate: (a: T) => boolean | Promise<boolean>,
): (r: Promise<IOption<T>>) => Promise<boolean>;
export function existsAsync<T>(
predicate: (a: T) => boolean | Promise<boolean>,
r: Promise<IOption<T>>,
): Promise<boolean>;

Defined in: promise-option/existsAsync.ts

import { existsAsync, filterAsync } from '@sandlada/result/promise-option';
import { ofSome, ofNone } from '@sandlada/result/option';
const input = Promise.resolve(ofSome(42));
const ok = await existsAsync(async (n) => n > 10, input);
const narrowed = await filterAsync(async (n) => n > 10, input);
// ok === true; narrowed resolves to Some(42)
const empty = Promise.resolve(ofNone<number>());
const missing = await existsAsync(async (n) => n > 10, empty);
// missing === false; existsAsync short-circuits on None

Filters the value of a Promise<IOption<T>> with a predicate. Keeps the Some if the predicate holds; converts to None otherwise. None passes through.

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 filterAsync<T>(
predicate: (a: T) => boolean | Promise<boolean>,
): (r: Promise<IOption<T>>) => Promise<IOption<T>>;
export function filterAsync<T>(
predicate: (a: T) => boolean | Promise<boolean>,
r: Promise<IOption<T>>,
): Promise<IOption<T>>;

Defined in: promise-option/filterAsync.ts

import { filterAsync, unwrapOrAsync } from '@sandlada/result/promise-option';
import { ofSome } from '@sandlada/result/option';
const input = Promise.resolve(ofSome(21));
const kept = filterAsync(async (x: number) => x > 10, input);
const value = await unwrapOrAsync(0, kept);
// value === 21
const rejected = filterAsync(async (x: number) => x > 100, Promise.resolve(ofSome(21)));
const fallback = await unwrapOrAsync(0, rejected);
// fallback === 0 because filterAsync returned None

Flattens a nested Promise<IOption<IOption<T>>>.

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

export function flattenAsync<T>(
r: Promise<IOption<IOption<T>>>,
): Promise<IOption<T>>;

Defined in: promise-option/flattenAsync.ts

import { flattenAsync, matchAsync } from '@sandlada/result/promise-option';
import { ofSome, ofNone } from '@sandlada/result/option';
const nested = Promise.resolve(ofSome(ofSome(42)));
const flat = await flattenAsync(nested);
const label = await matchAsync(
(n) => String(n),
() => 'empty',
Promise.resolve(flat),
);
// label === '42'
const wrappedNone = Promise.resolve(ofSome(ofNone<number>()));
const cleared = await flattenAsync(wrappedNone);
const absent = await matchAsync(
(n) => String(n),
() => 'empty',
Promise.resolve(cleared),
);
// absent === 'empty'; flattenAsync peeled Some off None

Transforms the value of a Promise<IOption<T>>. The callback may be sync or async.

Throw policy: if f throws synchronously or its returned Promise rejects, the result is None. The thrown reason is discarded.

export function mapAsync<T, U>(
f: (a: T) => U | Promise<U>,
): (r: Promise<IOption<T>>) => Promise<IOption<U>>;
export function mapAsync<T, U>(
f: (a: T) => U | Promise<U>,
r: Promise<IOption<T>>,
): Promise<IOption<U>>;

Defined in: promise-option/mapAsync.ts

import { mapAsync, matchAsync } from '@sandlada/result/promise-option';
import { ofSome, ofNone } from '@sandlada/result/option';
const input = Promise.resolve(ofSome(5));
const mapped = mapAsync((x: number) => x * 2, input);
const label = await matchAsync(
(n) => `value:${n}`,
() => 'nothing',
mapped,
);
// label === 'value:10'
const empty = Promise.resolve(ofNone<number>());
const skipped = mapAsync((x: number) => x * 2, empty);
const absent = await matchAsync(
(n) => `value:${n}`,
() => 'nothing',
skipped,
);
// absent === 'nothing'; mapAsync passes None through

Maps the value of Promise<IOption<T>>, returning a default on None. Mirrors mapOrAsync for the Option-flavored pipeline.

export function mapOrAsync<A, B>(
defaultValue: B,
fn: (a: A) => B | Promise<B>,
): (r: Promise<IOption<A>>) => Promise<B>;
export function mapOrAsync<A, B>(
defaultValue: B,
fn: (a: A) => B | Promise<B>,
r: Promise<IOption<A>>,
): Promise<B>;

Defined in: promise-option/mapOrAsync.ts

import { mapAsync, mapOrAsync } from '@sandlada/result/promise-option';
import { ofSome, ofNone } from '@sandlada/result/option';
const input = mapAsync((x: number) => x + 1, Promise.resolve(ofSome(20)));
const total = await mapOrAsync(-1, (x: number) => x * 2, input);
// total === 42
const missing = await mapOrAsync(-1, (x: number) => x * 2, Promise.resolve(ofNone()));
// missing === -1; mapOrAsync uses the default on None

Maps the value of Promise<IOption<T>>, or computes a default from a thunk on None. Mirrors mapOrElseAsync for the Option-flavored pipeline.

export function mapOrElseAsync<A, B>(
onNone: () => B | Promise<B>,
fn: (a: A) => B | Promise<B>,
): (r: Promise<IOption<A>>) => Promise<B>;
export function mapOrElseAsync<A, B>(
onNone: () => B | Promise<B>,
fn: (a: A) => B | Promise<B>,
r: Promise<IOption<A>>,
): Promise<B>;

Defined in: promise-option/mapOrElseAsync.ts

import { mapAsync, mapOrElseAsync } from '@sandlada/result/promise-option';
import { ofSome, ofNone } from '@sandlada/result/option';
const input = mapAsync((x: number) => x + 1, Promise.resolve(ofSome(20)));
const total = await mapOrElseAsync(() => -1, (x: number) => x * 2, input);
// total === 42
const missing = await mapOrElseAsync(() => -1, (x: number) => x * 2, Promise.resolve(ofNone()));
// missing === -1; mapOrElseAsync calls onNone when the carrier is None

Terminal — pattern-matches on both cases of an async option.

export function matchAsync<T, U>(
onSome: (a: T) => U | Promise<U>,
onNone: () => U | Promise<U>,
): (r: Promise<IOption<T>>) => Promise<U>;
export function matchAsync<T, U>(
onSome: (a: T) => U | Promise<U>,
onNone: () => U | Promise<U>,
r: Promise<IOption<T>>,
): Promise<U>;

Defined in: promise-option/matchAsync.ts

import { mapAsync, matchAsync } from '@sandlada/result/promise-option';
import { ofSome, ofNone } from '@sandlada/result/option';
const input = Promise.resolve(ofSome(42));
const doubled = mapAsync((n) => n * 2, input);
const label = await matchAsync(
(n) => `some: ${n}`,
() => 'none',
doubled,
);
// label === 'some: 84'
const empty = Promise.resolve(ofNone<number>());
const absent = await matchAsync(
(n) => `some: ${n}`,
() => 'none',
empty,
);
// absent === 'none'

Error recovery for async options.

Throw policy: if f throws synchronously or its returned Promise rejects, the result is None. The thrown reason is discarded.

export function orElseAsync<T>(
f: () => IOption<T> | Promise<IOption<T>>,
): (r: Promise<IOption<T>>) => Promise<IOption<T>>;
export function orElseAsync<T>(
f: () => IOption<T> | Promise<IOption<T>>,
r: Promise<IOption<T>>,
): Promise<IOption<T>>;

Defined in: promise-option/orElseAsync.ts

import { orElseAsync, unwrapOrAsync } from '@sandlada/result/promise-option';
import { ofSome, ofNone } from '@sandlada/result/option';
const recovered = orElseAsync(
() => Promise.resolve(ofSome(42)),
Promise.resolve(ofNone<number>()),
);
const value = await unwrapOrAsync(0, recovered);
// value === 42
const kept = orElseAsync(
() => Promise.resolve(ofSome(99)),
Promise.resolve(ofSome(5)),
);
const same = await unwrapOrAsync(0, kept);
// same === 5; orElseAsync leaves Some unchanged

Side-effect on the success track of an async option.

Throw policy: if fn throws synchronously or its returned Promise rejects, the result is None. The thrown reason is discarded.

export function tapAsync<T>(
fn: (a: T) => void | Promise<void>,
): (r: Promise<IOption<T>>) => Promise<IOption<T>>;
export function tapAsync<T>(
fn: (a: T) => void | Promise<void>,
r: Promise<IOption<T>>,
): Promise<IOption<T>>;

Defined in: promise-option/tapAsync.ts

import { tapAsync, matchAsync } from '@sandlada/result/promise-option';
import { ofSome, ofNone } from '@sandlada/result/option';
let seen = 0;
const input = Promise.resolve(ofSome(42));
const touched = tapAsync(async (v: number) => { seen = v; }, input);
const label = await matchAsync(
(n) => String(n),
() => 'none',
touched,
);
// seen === 42 and label === '42'
const empty = tapAsync(async () => { seen = -1; }, Promise.resolve(ofNone<number>()));
await matchAsync((n) => String(n), () => 'none', empty);
// seen stays 42; tapAsync passes None through without invoking the side effect

Side-effect on both tracks of Promise<IOption<T>>.

Two callbacks: fn runs on the Some track (with the inner value), fnNone runs on the None track - which is the failure track of an Option. Splitting the callbacks eliminates the (value: T | undefined) lie - on None there is genuinely no value, so the callback can’t pretend to receive one.

The Err in the name refers to the failure track of the Option, which is None: fnNone is the observer for it. fn observes the Some track, the same track tapAsyncOption observes.

Throw policy: a synchronous throw from either callback converts to None; a rejected Promise propagates as an outer rejection (promotion-family rule).

export function tapErrAsync<T>(
fn: (value: T) => void | Promise<void>,
fnNone?: () => void | Promise<void>,
): (r: Promise<IOption<T>>) => Promise<IOption<T>>;
export function tapErrAsync<T>(
fn: (value: T) => void | Promise<void>,
r: Promise<IOption<T>>,
fnNone?: () => void | Promise<void>,
): Promise<IOption<T>>;

Defined in: promise-option/tapErrAsync.ts

import { tapErrAsync, matchAsync } from '@sandlada/result/promise-option';
import { ofSome, ofNone } from '@sandlada/result/option';
let note = '';
const present = tapErrAsync(
(v: number) => { note = `some:${v}`; },
Promise.resolve(ofSome(7)),
);
const label = await matchAsync((n) => String(n), () => 'none', present);
// note === 'some:7' and label === '7'
const absent = tapErrAsync(
(_v: number) => { note = 'some'; },
Promise.resolve(ofNone<number>()),
() => { note = 'none'; },
);
await matchAsync((n) => String(n), () => 'none', absent);
// note === 'none'; fnNone runs on the None track

Extracts the value on success from an async option, or returns a default on failure.

The default value type D is independent of the success type T, so a wider or sentinel value can be supplied as a fallback — e.g. unwrapOrAsync<number, null>(null) for a Promise<IOption<User>> resolves to Promise<User | null>.

export function unwrapOrAsync<T = unknown, D = T>(
defaultValue: D | Promise<D>,
): <T2 extends T>(r: Promise<IOption<T2>>) => Promise<T2 | D>;
export function unwrapOrAsync<T, D = T>(
defaultValue: D | Promise<D>,
r: Promise<IOption<T>>,
): Promise<T | D>;

Defined in: promise-option/unwrapOrAsync.ts

import { mapAsync, unwrapOrAsync } from '@sandlada/result/promise-option';
import { ofSome, ofNone } from '@sandlada/result/option';
const prepared = mapAsync((x: number) => x * 2, Promise.resolve(ofSome(21)));
const value = await unwrapOrAsync(0, prepared);
// value === 42
const empty = Promise.resolve(ofNone<number>());
const fallback = await unwrapOrAsync(0, empty);
// fallback === 0

Lazily extracts the value of Promise<IOption<T>>, computing a default from a thunk on None. Returns Promise<T | D>. Mirrors unwrapOrElseAsync for the Option-flavored pipeline.

The default value type D is independent of the success type T.

export function unwrapOrElseAsync<T = unknown, D = T>(
onNone: () => D | Promise<D>,
): <T2 extends T>(r: Promise<IOption<T2>>) => Promise<T2 | D>;
export function unwrapOrElseAsync<T, D = T>(
onNone: () => D | Promise<D>,
r: Promise<IOption<T>>,
): Promise<T | D>;

Defined in: promise-option/unwrapOrElseAsync.ts

import { mapAsync, unwrapOrElseAsync } from '@sandlada/result/promise-option';
import { ofSome, ofNone } from '@sandlada/result/option';
const prepared = mapAsync((x: number) => x * 2, Promise.resolve(ofSome(21)));
const value = await unwrapOrElseAsync(() => 0, prepared);
// value === 42
const empty = Promise.resolve(ofNone<number>());
const fallback = await unwrapOrElseAsync(() => 0, empty);
// fallback === 0; unwrapOrElseAsync runs onNone when the carrier is None