p-map for controlled concurrency in JavaScript
Map over promises concurrently
At a glance
- What is it?
- p-map adapts Promise mapping operations with a concurrency limit, ordered results, configurable error behavior, and support for objects and Map instances. It is useful for independent asynchronous tasks, but it is not a limiter for promises that are already running.
- Who is it for?
- Adopt p-map when mapping independent asynchronous work requires a concurrency limit, ordered output, or explicit rejection handling. Do not use pMapSettled to constrain promises that started before being passed in.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 12 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Why p-map differs from Promise.all
p-map addresses a specific weakness in using `Promise.all()` directly: it offers no concurrency limit. An application that maps over hundreds of URLs, files, or service records could begin hundreds of operations before waiting for them, while `p-map` lets the caller set the number processed at once through the `concurrency` option. It also changes error policy. `pMap()` rejects as soon as an input or mapped operation rejects, while `pMapSettled()` records fulfillment and rejection for every item.
The target audience is JavaScript or TypeScript developers working with Node.js 18 or newer who already use asynchronous functions and need controlled task submission. The library returns an array in input order rather than completion order. That distinction matters when display order, positional joins, or downstream indexing must remain stable even though work finishes at different times.
This is a scheduling utility, not a general rate limiter for every asynchronous process. The documentation states that promises already present in the input are already running. Their concurrency therefore cannot be controlled by `pMapSettled()` or the other p-map functions. Callers should pass ordinary values when the mapper is responsible for starting the work.
The pMap execution and result model
The central API is `pMap(input, mapper, options?)`. It accepts input values, invokes `mapper` for those values, limits how many mappings run concurrently, waits until all accepted work finishes, and returns fulfilled mapper values in the original input order. If an input value or mapper result rejects, the returned promise rejects. This makes `pMap()` the closest p-map counterpart to `Promise.all()`.
The repository's main usage example combines a promise, a string, and another string in the input. Because one element is already a promise, that particular operation starts before p-map can apply a concurrency limit. The example still illustrates ordered output: the returned array follows `sites`, not the order in which the HTTP requests finish. Its `concurrency: 2` controls calls made through the mapper for values that have not already started.
`pMapSettled()` follows the shape of `Promise.allSettled()`. Successful calls produce `{status: 'fulfilled', value}` records, while rejected inputs or mapper calls produce `{status: 'rejected', reason}` records. A rejected input does not invoke the mapper. The mapper can return `pMapSkip` to omit a result and `pMapStop` to stop processing. The README excerpt also says that iteration errors and abort signals still reject the returned promise.
There is an important boundary here. Passing existing promises may cause an early rejection to be reported as unhandled before `pMapSettled()` reaches that item. The documentation's recommendation is direct: for promises that already exist, use `Promise.allSettled()`. With `pMapSettled()`, pass values and initiate asynchronous work inside `mapper`.
Object and Map support in the keyed APIs
`pMapKeyed(input, mapper, options?)` extends the mapping model to an object or a `Map`. Its mapper receives both the value and its key. The fulfilled result preserves the same keys and their order. An object produces an object with a `null` prototype, matching the documented behavior associated with the Stage 3 `Promise.allKeyed()` proposal. A `Map` input produces a new `Map`.
Object handling includes own enumerable string and symbol keys. For a `Map`, keys can be values. There is a cross-realm caveat documented in the README: a `Map` from `node:vm` or an iframe is treated as an object, so its entries are not mapped. That makes p-map unsuitable for that specific cross-realm input unless the caller converts it first.
The keyed implementation also differs in rejection handling from array inputs. All values are read immediately, and each value is awaited before its mapper runs. A rejected value receives a handler immediately, which the documentation says prevents an unhandled rejection even if the concurrency limit has not reached it yet. A `pMapSkip` result removes that key. A `pMapStop` result retains only the keys processed before it.
`pMapSettledKeyed()` adds settlement records to those outputs. Its result contains `{status: 'fulfilled', value}` or `{status: 'rejected', reason}` under each retained key. As with `pMapSettled()`, reading `input` can still throw and an aborted signal can still reject the overall operation. The keyed forms are most useful when a collection's identity matters, such as fetching two resources stored under semantic property names without flattening them into an unkeyed result.
Installing p-map and running a first bounded mapping
Install the package with npm, then import it as an ES module. The package metadata declares `"type": "module"`, requires Node.js 18 or newer, and publishes `index.js` plus `index.d.ts`. The following is a complete first use that starts each request inside the mapper, so `concurrency` has control over the deferred work rather than promises that began earlier.
npm install p-mapThe example asks `pMap` to process two URLs with a limit of two. The response records should be inspected under `user` and `posts`; the exact response contents depend on those external endpoints.
import {pMapKeyed} from 'p-map';
const {user, posts} = await pMapKeyed({
user: 'https://example.com/api/user',
posts: 'https://example.com/api/posts',
}, async url => {
const response = await fetch(url);
return response.json();
}, {concurrency: 2});
console.log({user, posts});The README presents the same API shape for `pMapKeyed()`. It also shows `pMap()` for arrays and `pMapSettled()` when each item's outcome must be inspected rather than rejecting the whole operation. Those variants differ in result shape, so callers should choose based on whether keys, fail-fast rejection, or per-item settlement records are required.
Node.js 18, ESM, and maintenance considerations
The current package metadata identifies version `7.1.1`, uses the MIT license, exposes only `index.js` and `index.d.ts` in the published files list, and sets `sideEffects` to `false`. Node.js `>=18` is required. A project on an older Node.js release cannot adopt this package version without upgrading the runtime or selecting an older compatible version. A project that cannot use ECMAScript modules also needs to account for the module configuration shown in `package.json`.
The license permits use, modification, distribution, and sublicensing under its conditions, with the warranty disclaimer and copyright notice retained in copies or substantial portions. That is a license summary, not legal advice. The project does not add runtime dependencies in the shown `package.json`, although development checks require packages such as `ava`, `xo`, and `tsd`.
Repository maintenance is current according to the available repository facts. The last push was on 2026-10-11, and releases `v7.1.1`, `v7.1.0`, and `v7.0.8` were published in October and September 2026. These dates indicate recent repository activity and release cadence, but the excerpt does not document a stable release schedule or a long-term support policy. Upgrade planning should account for the Node.js 18 floor and for reviewing behavior changes between major versions rather than assuming compatibility beyond the declared range.
Where p-map is the wrong tool
p-map is a poor choice when concurrency does not need control. For a small or fixed set of already-running promises that can safely run together, `Promise.all()` is simpler and returns the values directly. For an existing collection of promises whose completion status must be collected, `Promise.allSettled()` avoids the unhandled-rejection problem that the README identifies with `pMapSettled()`.
It also does not provide retries, timeouts, per-request rate limiting, durable job queues, or distributed scheduling. None of those behaviors is documented as part of its API. The `signal` option supports abortion according to the README excerpt, but aborting is not equivalent to retrying failed work or resuming it after a process restart. For workloads that require those properties, a dedicated queue or scheduling system with a different architecture would be more appropriate.
The package is also a poor match when the output must reflect completion order. All documented collection forms preserve input order or key order. Callers who genuinely want results grouped by finish time would need different logic and should not assume that setting `concurrency` changes result ordering.
Choosing between p-map and native Promise methods
The main alternative is the native Promise API. `Promise.all()` accepts already-running promises without a concurrency limit and fails when one rejects, while `pMap()` maps values through a function and can delay mapper starts according to `concurrency`. `Promise.allSettled()` records outcomes for promises that already exist, while `pMapSettled()` is designed for values whose work is initiated inside the mapper.
The keyed functions add another distinction. `pMapKeyed()` and `pMapSettledKeyed()` work without depending on the Stage 3 `Promise.allKeyed()` and `Promise.allSettledKeyed()` proposals being available in the JavaScript engine. Native `Promise.all()` remains the more direct tool when input is already an array of promises and unrestricted concurrency is acceptable.
Selection therefore comes down to where work starts and what the caller needs back. Use p-map for bounded mapper-driven concurrency and ordered output. Use native Promise methods when operations are already running or when no scheduling behavior is needed.
Editorial conclusion
Adopt p-map when mapping independent asynchronous work requires a concurrency limit, ordered output, or explicit rejection handling. Do not use pMapSettled to constrain promises that started before being passed in. Before adoption, verify that the work supports Node.js 18 or newer, confirm that deferred tasks rather than pre-created promises are passed as input, and choose the error mode that matches the caller's requirements.
Frequently asked questions
How do I install p-map?
Install it with `npm install p-map`. The current package metadata requires Node.js 18 or newer and declares the package as an ES module.
How does p-map limit concurrency?
Use the `concurrency` option to limit how many mapper operations run at once. The limit applies to work started through the mapper; promises already running in the input cannot be constrained.
When should I use pMapSettled instead of Promise.allSettled?
Use `pMapSettled()` when the mapper should start the asynchronous work and p-map should limit that work while recording each outcome. The README recommends `Promise.allSettled()` for promises that were already created before they were passed in.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/sindresorhus-p-map)Was this useful?