mapConcurrent
EditMaps values asynchronously with bounded concurrency and source-ordered results.
/** * Maps values asynchronously with bounded concurrency and source-ordered results. * After the first failure, no idle worker starts another callback and the returned * promise rejects immediately. Callbacks already in flight may settle in the background. */export type MapConcurrentTransform<T, Return> = ( this: void, value: T, index: number, array: readonly (T | undefined)[]) => Return | PromiseLike<Return>;
export const mapConcurrent = async <T, Return>( array: readonly T[], concurrency: number, transform: MapConcurrentTransform<T, Return>): Promise<Array<Awaited<Return>>> => { if (!Number.isSafeInteger(concurrency) || concurrency < 1) { throw new RangeError("concurrency must be a positive safe integer"); }
const entries: Array<[sourceIndex: number, value: T]> = []; for (let index = 0; index < array.length; index += 1) { if (Object.hasOwn(array, index)) { entries.push([index, array[index] as T]); } } const results = new Array<Awaited<Return>>(entries.length); let cursor = 0; let failed = false; let rejectOnFailure: (error: unknown) => void = () => {}; const failure = new Promise<never>((_, reject) => { rejectOnFailure = reject; });
const worker = async () => { while (!failed) { const resultIndex = cursor; cursor += 1; const entry = entries[resultIndex]; if (!entry) return; const [sourceIndex, value] = entry;
try { results[resultIndex] = await transform(value, sourceIndex, array); } catch (error) { if (!failed) { failed = true; rejectOnFailure(error); } } } };
const completion = Promise .all(Array.from({ length: Math.min(concurrency, entries.length) }, worker)) .then(() => results); return Promise.race([completion, failure]);};Download
Section titled “Download”wget -O src/lib/mapConcurrent.ts https://raw.githubusercontent.com/jrTilak/lazykit/HEAD/registry/functions/mapConcurrent.tstemp_dir="$(mktemp -d)" && bunx degit jrTilak/lazykit/registry/functions "$temp_dir" && mv "$temp_dir/mapConcurrent.ts" src/lib/mapConcurrent.ts && rm -r "$temp_dir"Examples
Section titled “Examples”import { mapConcurrent, type MapConcurrentTransform } from "./mapConcurrent";
const urls = ["/api/first", "/api/second"];
const fetchPage: MapConcurrentTransform<string, string> = async (url) => { const response = await fetch(url); if (!response.ok) throw new Error(`Request failed with ${response.status}`); return response.text();};
const pages: string[] = await mapConcurrent(urls, 3, fetchPage);array(readonly T[]) — Source values. Empty slots are skipped; callback indexes still refer to the original array.concurrency(number) — Positive safe-integer limit.transform((value: T, index, array: readonly (T | undefined)[]) => R | PromiseLike<R>) — Receives an always-present value, its original index, and the original source array, which retains skipped holes.- Results retain source order even when callbacks finish out of order.
- After the first rejection, idle workers stop taking values. Callbacks already in flight continue safely in the background while the returned promise rejects immediately with the first observed error.