Async Pool Concurrent Map Utility

Map over large collections with bounded async concurrency, preserving input order in results and stopping early on failure modes.

TypescriptResilience

Code

snippet
async function asyncPool<T, R>(
  concurrency: number,
  items: readonly T[],
  mapper: (item: T, index: number) => Promise<R>,
): Promise<R[]> {
  const results = new Array<R>(items.length);
  let cursor = 0;

  const workers = Array.from({ length: Math.min(concurrency, items.length) }, async () => {
    while (cursor < items.length) {
      const index = cursor;
      const item = items[index];
      cursor += 1;
      results[index] = await mapper(item, index);
    }
  });

  await Promise.all(workers);
  return results;
}

const CONCURRENCY = 4;

const enriched = await asyncPool(CONCURRENCY, ['a', 'b', 'c', 'd', 'e', 'f'], async (id, i) => {
  await new Promise((r) => setTimeout(r, 20));
  return { id, position: i };
});

console.log('order preserved:', enriched.map((e) => e.id).join(''));

const withFailure = await asyncPool(2, ['x', 'y', 'z'], async (id) => {
  if (id === 'y') throw new Error('boom on ' + id);
  return id.toUpperCase();
}).catch((err) => err.message);

console.log('failure propagated:', withFailure);