TypeScript: running async tasks with a concurrency limit

Starting thousands of asynchronous jobs at once can overwhelm a server, a database, or the network. A pool limits how many run at the same time. The pool has N workers, and each worker repeatedly takes the next unstarted task, awaits it, and stores its result at the task's own index.

TypeScript
async function tick(times: number): Promise<void> {
  for (let i = 0; i < times; i++) {
    await null;
  }
}

async function pool<T>(tasks: (() => Promise<T>)[], limit: number): Promise<T[]> {
  const results: T[] = new Array(tasks.length);
  let next = 0;
  async function worker(): Promise<void> {
    while (next < tasks.length) {
      const index = next++;
      results[index] = await tasks[index]();
    }
  }
  await Promise.all(Array.from({ length: limit }, () => worker()));
  return results;
}

let running = 0;
let peak = 0;
const finished: string[] = [];

function task(name: string, ticks: number): () => Promise<string> {
  return async () => {
    running++;
    peak = Math.max(peak, running);
    await tick(ticks);
    running--;
    finished.push(name);
    return name.toUpperCase();
  };
}

const tasks = [task("a", 10), task("b", 1), task("c", 2), task("d", 1)];
pool(tasks, 2).then((results) => {
  console.log("results in input order: " + results.join(","));
  console.log("finished order: " + finished.join(","));
  console.log("peak concurrency: " + peak);
});
Read the full lesson