Skip to content

tap

Observes a value without modifying it. tap is useful for logging, debugging, metrics, or any situation where you want to inspect a value inside a pipeline without affecting the pipeline’s output.

Never mutates input and never throws. If the callback throws or returns a rejected Promise, tap returns a rejected Promise instead of propagating the throw.

Signature

function tap<T>(value: T, fn: (value: T) => void | Promise<void>): T | Promise<T>

Parameters

Returns

One of:

  • T — if the callback is synchronous and does not throw.
  • Promise<T> — if the callback is asynchronous.
  • Promise<T>, rejecting with an error — if the callback throws or rejects.

Behavior

  • Pure function: no side effects except those explicitly performed inside the callback.
  • Never mutates input.
  • Never throws.
  • Returns the original value unchanged.
  • If the callback returns a Promise, the pipeline becomes asynchronous.
  • If the callback throws, the pipeline becomes asynchronous and returns a rejected Promise.

Examples

pipe(
  user,
  tap(u => console.log("User:", u)),
  normalizeUser,
  validateUser
)

await tap(5, async n => {
  await saveMetric("value_seen", n);
});

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

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

Notes

  • Intentionally minimal: it never alters the value.
  • Useful for debugging, logging, metrics, or instrumentation.
  • Works seamlessly with pipe, compose, maybe, and tryOrDefault.
  • Because it never throws, it is safe to use inside validation and normalization pipelines.