Map AsyncLoading saved progress…

Map Async

Implement mapAsync(items, asyncMapper) — the async cousin of Array.prototype.map. You take an iterable of items and a mapper that returns a promise. You apply the mapper to every item in parallel and resolve with the results array, preserved in input order. If any mapper rejects, the returned promise rejects with that reason — same fast-fail behaviour as Promise.all.

Signature

// Returns a Promise that fulfils with results[] in input order.
// Rejects on the first mapper rejection (or synchronous throw).
function mapAsync<T, U>(
  items: Iterable<T>,
  asyncMapper: (item: T, index: number) => Promise<U> | U,
): Promise<U[]>;

Examples

// Basic parallel fetch. All three requests fire at once;
// the result settles when the slowest one finishes.
const ids = [1, 2, 3];
const users = await mapAsync(ids, (id) => fetch(`/api/users/${id}`).then((r) => r.json()));
// users === [{ id: 1, ... }, { id: 2, ... }, { id: 3, ... }]
// Errors propagate. The first rejection wins; results from later
// successful mappers are discarded.
await mapAsync([1, 2, 3], async (n) => {
  if (n === 2) throw new Error('boom');
  return n * 10;
}); // throws Error('boom')
// Empty input resolves to an empty array on the next microtask.
await mapAsync([], async (x) => x); // []

Notes

  • Parallel by default. Every mapper call is kicked off in the same synchronous pass; you do not await each one before starting the next. With 5 items at 50ms each, the wall-clock time should be ~50ms, not 250ms.
  • Input order, not settle order. results[i] must hold the value asyncMapper(items[i], i) resolved to — even when that mapper finishes last.
  • Mapper receives (item, index). Same shape as Array.prototype.map. The third array argument is not required.
  • Fast-fail on rejection. As soon as any mapper rejects, the returned promise rejects with that reason. In-flight mappers keep running (promises can't be cancelled) but their results are ignored.
  • Synchronous throws count as rejections. If the mapper throws synchronously (e.g. it accesses item.foo on undefined), wrap that into a rejection — don't let it escape to the caller.
  • Non-promise return values are valid. A mapper that returns 42 directly should work the same as one that returns Promise.resolve(42). Wrap with Promise.resolve so the code path is uniform.
  • Accepts any iterable. Arrays, generators, Sets — anything with a Symbol.iterator.
Loading editor…