Concurrency Patterns

waiting for multiple Promises to settle

all settled:

await Promise.allSettled([p1, p2, p3])
console.log(await Promise.allSettled([
  new Promise(resolve => setTimeout(resolve)),
  "value",
  Promise.reject(new Error("error")),
]))

Expected output:

[
  { status: "fulfilled", value: undefined },
  { status: "fulfilled", value: "value" },
  {
    status: "rejected",
    reason: Error: error
  }
]

all fulfilled or first rejected:

await Promise.all([p1, p2, p3])

first fulfilled, reject only when all rejected:

await Promise.any([p1, p2, p3])

first settled:

await Promise.race([p1, p2, p3])

limiting concurrency

runWithConcurrencyLimit executes an array of asynchronous tasks with a specified concurrency limit, ensuring that no more than the given number of tasks run concurrently and returning the results once all tasks are completed.

function runWithConcurrencyLimit<T>(
  tasks: Array<() => Promise<T>>,
  concurrency: number
): Promise<Array<T>> {
  return new Promise((resolve, reject) => {
    if (tasks.length === 0) {
      resolve([])
    }

    const results: Array<T> = []
    let currentIndex = 0
    let completed = 0

    const next = () => {
      const index = currentIndex++
      tasks[index]().then(result => {
        results[index] = result
        if (++completed >= tasks.length) {
          resolve(results)
          return
        }
        if (currentIndex < tasks.length) {
          next()
        }
      }, reject)
    }

    for (let i = 0; i < Math.min(concurrency, tasks.length); i++) {
      next()
    }
  })
}