Skip to content

mapConcurrent

Edit

Maps values asynchronously with bounded concurrency and source-ordered results.

mapConcurrent.ts
/**
* 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]);
};
Terminal
wget -O src/lib/mapConcurrent.ts https://raw.githubusercontent.com/jrTilak/lazykit/HEAD/registry/functions/mapConcurrent.ts
mapConcurrent.example.ts
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.