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.