pmap-bounded
v0.2.1
Published
Bounded-concurrency Promise.all and Promise.allSettled. AbortSignal support, optional collect-all-errors mode. Zero dependencies.
Downloads
19
Maintainers
Readme
pmap-bounded
Promise.allandPromise.allSettledwith a concurrency limit,AbortSignalsupport, and an optional collect-all-errors mode. Zero dependencies.
import { pmap, pmapSettled } from "pmap-bounded";
const responses = await pmap(urls, (u) => fetch(u), { concurrency: 5 });
const results = await pmap(items, work, {
concurrency: 10,
stopOnError: false,
});
const settled = await pmapSettled(items, work, { concurrency: 4 });
for (const r of settled) {
if (r.status === "fulfilled") use(r.value);
else log(r.reason);
}Install
npm install pmap-boundedWorks with Node 20+, browsers, Bun, Deno. ESM + CJS.
Why
Promise.all runs everything in parallel — which for a thousand URLs is a thousand simultaneous fetches. That hits rate limits, exhausts file descriptors, or hammers the database.
pmap-bounded is Promise.all with a knob: concurrency: 10. Plus the things you actually want in real code:
AbortSignalto cancel the whole batchstopOnError: falseto keep going and aggregate errorspmapSettledfor "never throw, return per-item status"- Preserves input order in the output array
Recipes
Bulk fetch with rate limiting
import { pmap } from "pmap-bounded";
const responses = await pmap(
urls,
async (u) => {
const r = await fetch(u);
return r.json();
},
{ concurrency: 5 },
);Process all, collect errors instead of failing fast
import { pmap } from "pmap-bounded";
try {
const okResults = await pmap(items, work, { concurrency: 10, stopOnError: false });
} catch (err) {
if (err instanceof AggregateError) {
console.error(`${err.errors.length} failed:`);
for (const e of err.errors) console.error(e);
}
}Use settled when you want per-item status without throwing
import { pmapSettled } from "pmap-bounded";
const results = await pmapSettled(urls, fetch, { concurrency: 5 });
const successes = results.filter((r) => r.status === "fulfilled").length;
console.log(`${successes}/${results.length} succeeded`);Total deadline
import { pmap } from "pmap-bounded";
const totalDeadline = AbortSignal.timeout(30_000);
const results = await pmap(items, work, {
concurrency: 5,
signal: totalDeadline,
});
// Throws with the timeout reason if 30s elapses before completionCombine with pretry
import { pmap } from "pmap-bounded";
import { retry, isRetriableHttpError } from "@p-vbordei/pretry";
await pmap(
urls,
(u) => retry(() => fetch(u), { retryOn: isRetriableHttpError }),
{ concurrency: 5 },
);API
pmap(items, mapper, opts?): Promise<R[]>
Preserves input order. The mapper receives (item, index).
| Option | Type | Default | Meaning |
|---|---|---|---|
| concurrency | number | Infinity | Max in-flight mappers |
| stopOnError | boolean | true | If false, all items are processed; an AggregateError is thrown at the end |
| signal | AbortSignal | — | Aborts the operation |
pmapSettled(items, mapper, opts?): Promise<PromiseSettledResult<R>[]>
Same options minus stopOnError. Returns one settled result per input. Only the signal abort can cause it to reject.
When to use what
| Want | Use |
|---|---|
| Bounded Promise.all semantics, fail-fast | pmap(...) |
| Bounded Promise.all, collect-all errors | pmap(..., { stopOnError: false }) → throws AggregateError |
| Bounded Promise.allSettled semantics | pmapSettled(...) |
| Job queue with priorities, abort, idle awaiting | pqueue-tiny |
License
Apache-2.0 © Vlad Bordei
