Skip to content

compose

Creates a right‑to‑left function composition pipeline. Each function receives the output of the next function in the chain. Supports both synchronous and asynchronous functions and guarantees that it never mutates input and never throws. If a function throws synchronously, the pipeline returns a rejected Promise instead of propagating the throw.

Use compose when you want a predictable, intention‑revealing transformation chain that reads from right to left.

Signature

function compose<T>(value: T): T;
function compose<T, A>(value: T, fn1: (v: T) => A | Promise<A>): A | Promise<A>;
function compose<T, A, B>(
  value: T,
  fn1: (v: T) => A | Promise<A>,
  fn2: (v: A) => B | Promise<B>
): B | Promise<B>;
// Additional overloads for longer pipelines…

Parameters

Returns

One of:

  • T — if no functions are provided.
  • The final transformed value — if all functions are synchronous and none throw.
  • A Promise resolving to the final value — if any function is asynchronous.
  • A Promise rejecting with an error — if any function throws synchronously or returns a rejected Promise.

Behavior

  • Pure function: no side effects.
  • Never mutates input.
  • Never throws.
  • Synchronous throws inside pipeline functions are converted into rejected Promises.
  • Applies functions right‑to‑left.
  • If no functions are provided, returns the initial value unchanged.
  • If any function returns a Promise, the entire pipeline becomes asynchronous.
  • If a function throws, the pipeline becomes asynchronous and returns a rejected Promise.

Examples

compose(2, n => n + 1, n => n * 2)
// inc(double(2)) = inc(4) = 5

await compose(3, n => n + 1, async n => n * 2)
// inc(asyncDouble(3)) = inc(6) = 7

const bad = () => {
  throw new Error("fail");
};

await compose(1, bad)
// Promise.reject(Error("fail"))

compose("hello")
// "hello"

Notes

  • The right‑to‑left counterpart to pipe.
  • It is intentionally minimal: no branching, no cleverness, no mutation.
  • Because it never throws, it is safe to use inside validation, normalization, and result pipelines.
  • Works seamlessly with tap, maybe, and tryOrDefault.