Learn · Concurrency
Async work and order
Asynchronous ETL needs an explicit answer to two questions: how much work may run, and in which order may results leave?
Set the bound
const enriched = exstream(orderIds).mapAsync(
async (id, context) => {
const response = await fetch(`/customers/${id}`, {
signal: context.signal,
})
return response.json()
},
{ concurrency: 8 },
) Open “Async work and order” in the playground concurrency: 8 allows at most eight active callbacks. The default is one. The bound limits parallel I/O; downstream backpressure may reduce the active work further.
Choose the order
ordered: true is the default. A fast later result waits until every earlier result is ready. This makes output predictable but can create head-of-line blocking.
const fastestFirst = orders.mapAsync(enrichOrder, {
concurrency: 16,
ordered: false,
}) Use unordered output only when the destination does not depend on source order. The concurrency bound still applies.
Bound failure policy
const enriched = orders.mapAsync(enrichOrder, {
concurrency: 8,
ordered: true,
retry: 2,
timeout: 5_000,
}) retry: 2 means two additional attempts after the first failure. timeout applies to each attempt. The record context contains a signal that is cancelled when the branch no longer needs the work.
Retained work
Concurrency is not the only retention bound. Ordered output may hold completed later results behind one slow earlier result. The downstream destination may also stop accepting output temporarily.
Document all three choices together: concurrency, order, and downstream buffering. Continue with backpressure to see how they interact across the graph.