Appearance
Async Pipe
Compose async functions into a sequential pipeline where each function receives the output of the previous one.
When to Use
- Chaining multiple async transformations on a value
- Building data processing pipelines (fetch → transform → store)
- Replacing deeply nested
.then()chains with a declarative pipeline
The Pattern
Data loading pipeline
javascript
const loadUser = asyncPipe(
id => fetch(`/api/users/${id}`),
response => response.json(),
user => enrichWithProfile(user),
);
const user = await loadUser(42);Database query pipeline
javascript
const listAccounts = asyncPipe(
sql => query(sql),
accounts => accounts.map(withHoldings),
promises => Promise.all(promises),
accounts => accounts.map(toRecord),
);
const accounts = await listAccounts(
AccountQuery.list
);Process cleanup pipeline
javascript
const killProcessTree = asyncPipe(
() => pidtree(pid, {
advanced: true,
root: true,
}),
reverse(),
map(process => process.pid),
map(pid => killPromisified(pid)),
promises => Promise.allSettled(promises),
);Implementation
Each function in the pipeline receives the resolved value from the previous step. The initial input can be a value or a promise.
javascript
function asyncPipe(...fns) {
return async function (value) {
value = await value;
for (const fn of fns) {
value = await fn(value);
}
return value;
};
}Trade-offs
| Approach | Pros | Cons |
|---|---|---|
asyncPipe | Declarative, composable | Harder stack traces |
Chained await | Easy to debug | Repetitive |
.then() chains | No async/await needed | Deeply nested |
When to avoid: If the pipeline has complex branching or error recovery between steps, sequential await with explicit conditionals is clearer. asyncPipe works best for linear transformations.