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.
// 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[]>;
// 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); // []
results[i] must hold the value asyncMapper(items[i], i) resolved to — even when that mapper finishes last.(item, index). Same shape as Array.prototype.map. The third array argument is not required.item.foo on undefined), wrap that into a rejection — don't let it escape to the caller.42 directly should work the same as one that returns Promise.resolve(42). Wrap with Promise.resolve so the code path is uniform.Sets — anything with a Symbol.iterator.