Composition utilities
The Composition module is the engine room of this library. Beyond pipe and flow, it provides a collection of small, focused utilities to shape, branch, cache, and adapt functions before they enter a pipeline.
This guide acts as a comprehensive reference for these tools. If you are new to the concepts of piping and flow, start with Thinking in pipelines first.
pipe, flow, and compose
Section titled “pipe, flow, and compose”These three functions are different ways of sequencing transformations.
pipe evaluates a starting value immediately through a sequence of steps. flow defers execution, returning a reusable function.
compose is the right-to-left counterpart of flow. While flow(f, g) executes f first and g second, compose(g, f) runs them in reverse order, matching traditional mathematical notation ($g \circ f$):
Use compose primarily when adapting code from third-party libraries that rely on right-to-left composition patterns.
pipe namespace extensions
Section titled “pipe namespace extensions”Piping raw values through standard functions works well for clean, linear logic. However, real-world applications frequently require optional branches, safe error handling, object reshaping, or asynchronous steps. Rather than forcing you to write noisy inline arrow functions, the pipe namespace provides several specialized extensions.
Conditional steps
Section titled “Conditional steps”Real-world flows often involve optional steps, such as applying a discount only if a user is a VIP. In standard code, this often results in verbose inline conditions:
pipe.when and pipe.unless allow you to describe these conditions declaratively, executing the step only when the predicate evaluates to true (or false):
pipe.either functions like an inline ternary branch, picking one of two functions to execute based on a condition:
Safe steps with try
Section titled “Safe steps with try”Certain functions (like JSON.parse or filesystem reads) throw runtime exceptions. Wrapping a single step in a try/catch block breaks the visual flow of a pipeline.
pipe.try intercepts exceptions for a specific step, allowing you to handle the error and supply a safe fallback value:
Reshaping objects with struct
Section titled “Reshaping objects with struct”When deriving a structured object from a single input value, we often write verbose mapping wrappers.
pipe.struct allows you to construct a fresh object by running a record of field-level transformer functions on the piped value:
Short-circuiting nulls with safe
Section titled “Short-circuiting nulls with safe”Optional chaining (?.) is highly convenient, but when piping functions, you often have to guard each step to avoid runtime failures on empty values: (x) => x ? f(x) : null.
pipe.safe automatically short-circuits and propagates null or undefined the moment any intermediate step evaluates to a nil value:
Asynchronous steps with async
Section titled “Asynchronous steps with async”Standard pipelines expect synchronous steps. If a step returns a Promise, all subsequent steps receive the promise object itself instead of the resolved value.
pipe.async resolves promises returned at any stage before passing the resolved value to the next step, returning a final Promise:
flow namespace extensions
Section titled “flow namespace extensions”Symmetrically to pipe, the flow namespace provides extensions that compile these branching, catching, and asynchronous behaviors into reusable, deferred functions.
Conditional composition
Section titled “Conditional composition”flow.when and flow.unless let you inject optional steps into a reusable pipeline:
flow.either generates a branching step that picks one of two paths:
Safe composition with try
Section titled “Safe composition with try”flow.try creates a reusable step that wraps a throwing operation in a safety net, converting exceptions into default or fallback values:
Structured objects with struct
Section titled “Structured objects with struct”flow.struct compiles a record of transformer functions into a single mapping function that produces a structured object:
Short-circuiting nulls with safe
Section titled “Short-circuiting nulls with safe”flow.safe creates a reusable pipeline that safely propagates null or undefined down the chain, short-circuiting on nil values:
Asynchronous composition with async
Section titled “Asynchronous composition with async”flow.async creates an asynchronous composition chain, awaiting intermediate promises before moving to the next transformation:
Currying functions
Section titled “Currying functions”A curried function accepts its arguments one at a time. curry converts a standard two-argument function into a sequence of single-argument functions:
This is highly useful for preparing multi-argument functions to slot as steps inside pipe or flow. curry3 and curry4 handle three- and four-argument functions respectively:
Reversing currying
Section titled “Reversing currying”uncurry is the inverse of curry. It converts a curried, single-argument function back into a standard multi-argument format. This is useful when passing curried library helpers to external APIs that expect standard JavaScript functions:
uncurry3 and uncurry4 provide the same reversal for three- and four-argument signatures.
Flipping argument order
Section titled “Flipping argument order”flip reverses the argument order of a curried binary function. Its main purpose is to adapt data-last library functions to a data-first format for use outside of pipelines:
Side effects with tap
Section titled “Side effects with tap”tap executes a function for its side effect (such as logging or performance auditing) and returns the original value unchanged, leaving the pipeline’s behavior undisturbed:
Function primitives
Section titled “Function primitives”identity returns its input argument exactly as received. It is commonly used as a fallback callback where a no-op transformation is required:
constant returns a function that always yields the same fixed value, ignoring whatever input is supplied to it:
constTrue, constFalse, constNull, constUndefined, and constVoid are optimized shortcuts for common static returns.
once ensures a function is only executed on its first invocation, caching and returning that original result for all subsequent calls:
Predicate combinators
Section titled “Predicate combinators”not inverts a predicate function:
and and or combine two predicate functions with standard short-circuit evaluation:
Caching results
Section titled “Caching results”memoize wraps a function, caching the results of repeated calls using the input argument as the cache key:
For complex object keys, you can supply a custom key-generator function to evaluate equivalence structurally:
memoizeWeak performs the same caching behavior but stores entries using a WeakMap, ensuring that cache entries are automatically garbage-collected when the key object is no longer referenced in memory:
Fan out and combine
Section titled “Fan out and combine”converge takes a combining function and an array of transformers. It applies a single input value to each transformer independently, passing the collected results to the combiner:
Parallel results with juxt
Section titled “Parallel results with juxt”juxt is the simpler sibling of converge. It applies a single input to an array of transformer functions and returns all outcomes in a typed tuple:
Comparing by projection
Section titled “Comparing by projection”on takes a binary comparator and a projection function. It projects both input arguments before comparing them, removing the need to repeat key lookups in sorting and equality functions:
When to use each utility
Section titled “When to use each utility”pipe— Sequence transformations top-to-bottom on an immediate value.pipe.when/pipe.unless— Run a step conditionally in a pipeline.pipe.either— Branch a pipeline step to one of two paths based on a condition.pipe.try— Wrap a single throwing step with an error fallback.pipe.struct— Reshape a single input value into a structured object of transforms.pipe.safe— Short-circuit and propagatenull/undefinedacross steps automatically.pipe.async— Sequence transformations containing asynchronous transitions.flow— Build a reusable, deferred transformation function.compose— Sequence reusable transformations right-to-left.curry/curry3/curry4— Convert multi-argument functions to curried steps.uncurry/uncurry3/uncurry4— Convert curried functions back to multi-argument format.flip— Reverse the argument order of a curried binary function.identity— A no-op callback that returns its input exactly as received.constant— Create a function that always returns a static value.once— Ensure a function executes only on its first invocation.not/and/or— Combine and invert predicate functions.memoize— Cache results using primitive keys.memoizeWeak— Cache results using object keys, allowing garbage collection.tap— Execute a side effect in a pipeline without altering the value.converge— Fan out an input through multiple transformers and combine the results.juxt— Fan out an input through multiple transformers and collect them in a tuple.on— Build comparators or equality checks by comparing projected properties.