Async JavaScript

Concurrency Limits & Backpressure

JavaScript can start many async operations, but remote services, memory, CPU, file descriptors, queues, and databases are finite. Concurrency limits and backpressure keep throughput from becoming self-inflicted denial-of-service.
  • Async is not unlimited parallelism
  • Concurrency limits cap in-flight work
  • Backpressure lets consumers slow producers
  • Retries need budgets
  • Rate limits are external constraints
Limited concurrency
Only a fixed number of tasks are in flight at once.
Simple mapLimit
async function mapLimit(items, limit, fn) {
  const results = []
  const executing = new Set()

  for (const item of items) {
    const p = Promise.resolve().then(() => fn(item))
    results.push(p)
    executing.add(p.finally(() => executing.delete(p)))

    if (executing.size >= limit) {
      await Promise.race(executing)
    }
  }

  return Promise.all(results)
}
A small limiter is often better than a surprise stampede.
Sources
  • Node.js Documentation and Learn Node.jsConcurrency and streams backpressure
  • The Modern JavaScript TutorialAsync iteration
  • MDN JavaScript Guide and ReferencePromises