mrkeyoor.com_
Sun 20 Sept 04:56 UTC
npmUtilsupdated 20 Sept 2026

p-map review

p-map 7.0.6 runs an async mapper over a sync or async iterable while limiting pending mapper calls. Our browser build was 2.8 KB minified and 1.1 KB gzipped, and the package has no dependencies. pMap() collects values in input order; pMapIterable() yields them in that same order and limits completed values waiting on a slower consumer. Options cover aggregate failure handling and AbortSignal, while pMapSkip omits selected outputs. Release 7.0.6 fixes cleanup of an abort listener when the signal is already aborted. Concurrency is its boundary: priorities, starts per second, and a persistent queue belong elsewhere.

58.3Mdownloads / wk
Verdict

Our p-map 7.0.6 install took 0.3 seconds, left one 1 MB package, bundled to 1.1 KB gzipped, and had no audit findings, so it is an easy addition for ordered async mapping with a finite concurrency limit. Choose a queue for priorities or rate limits, and pass AbortSignal into the mapper for real cancellation.

We installed it

Lab card: what happened when we installed p-mapScreenshot of p-map documentation
Install✓ · 0.3s1 package on disk · 1 MB
ImportESM import works · require() works · ESM package with exports map
Browser1.1 KBgzipped (2.8 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does p-map install cleanly?

Yes. In a fresh container with an empty cache, npm install p-map finished in 0.3s, leaving 1 package and 1 MB on disk. npm audit reported no known vulnerabilities.

How much does p-map add to a browser bundle?

1.1 KB gzipped (2.8 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does p-map work with both ESM and CommonJS?

Yes. Both import 'p-map' and require('p-map') worked in Node 22 in our run. The package is published as ESM with an exports map.

Does p-map include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

p-map or p-limit: which should you use?

p-limit: Choose it when unrelated functions must share one concurrency counter. Our p-map 7.0.6 install took 0.3 seconds, left one 1 MB package, bundled to 1.1 KB gzipped, and had no audit findings, so it is an easy addition for ordered async mapping with a finite concurrency limit.

When should you not use p-map?

Promise.all() is enough when every operation may start immediately and the input already contains promises

API stability4/5pMap(input, mapper, options) remains the complete main call in version 7.0.6, with concurrency, stopOnError, and signal options. Version 7 also exposes pMapIterable() and requires Node 18. Recent patches fixed async iterable indexes and already-aborted signal cleanup without changing normal mapper calls. ESM packaging and the Node floor remain real upgrade boundaries for older applications.
Docs5/5The 7.0.6 README specifies both mapping functions, input types, mapper arguments, result order, every option, AggregateError, pMapSkip, AbortSignal, and backpressure. It plainly states that started mappers continue after rejection and that concurrency does not enforce a request rate. A p-throttle recipe shows the missing policy instead of implying that one concurrency number solves API quotas.
Maintenance4/5GitHub shows that the repository is not archived, has 12 open issues and pull requests, and was pushed on 2026-07-20. Release 7.0.6 shipped the same day with a fix for abort-listener cleanup on an already-aborted signal. The package has 0 dependencies and a narrow API, so this targeted patch is stronger maintenance evidence than raw commit frequency for such a small module.
Ecosystem5/5npm recorded 81,799,240 downloads in the latest weekly period, and GitHub showed 1,510 stars. The related p-limit, p-queue, p-throttle, p-filter, and p-map-series packages cover adjacent scheduling jobs. Our install found 0 dependencies, bundled TypeScript declarations, working import paths, and a 1.1 KB gzipped browser build, making the utility inexpensive in a modern toolchain.

Use it if

  • pMap 7 can put a finite concurrency limit around one async operation over a known or generated sequence
  • Outputs must retain input order although mapper calls finish in another order
  • pMapIterable() should keep a slow database consumer from accumulating every completed result
  • pMapSkip or AggregateError matches a batch that omits selected values or reports several failures together
Skip it if

Setup reality

We installed p-map 7.0.6 in 0.3 seconds in a fresh, unprivileged Node 22 Bookworm sandbox with 3 CPUs and 8 GB of RAM. One package occupied 1 MB, and npm audit returned zero findings at every severity. The package declares 0 direct and 0 peer dependencies, is 36 KB unpacked, and bundles TypeScript declarations. It publishes ESM with an exports map; ESM import and require() both worked in our Node 22 check.

Our esbuild entry measured 2.8 KB minified and 1.1 KB gzipped. No credentials or config file exist. The important default is concurrency:Infinity, which gives an API or database no protection until code supplies a finite number. pMap() preserves input order. An async iterable is accepted, and p-map may have several next() requests pending up to the chosen concurrency, so a stateful producer must tolerate overlapping reads.

stopOnError defaults to true, rejecting after the first observed mapper failure while already started calls continue. Setting it false waits for the batch and then throws AggregateError if anything failed; successful outputs are not returned alongside that error. pMapSkip removes an item completely, so result indexes after a skipped value no longer correspond to input indexes. Neither mode performs retries.

The signal option is available on pMap(), not pMapIterable(). Aborting rejects the outer operation, but arbitrary work inside the mapper continues unless the same signal reaches fetch or another cancellable API. pMapIterable() uses backpressure to cap resolved values waiting on its consumer, and that number must be at least the concurrency value. Ordered yielding means one slow first item can hold later completed work despite the 1.1 KB helper's concurrency.

Patterns

Fetch records 4 at a time map-with-concurrency

import pMap from 'p-map';

const records = await pMap(ids, async id => {
  const response = await fetch(`/api/records/${id}`);
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json();
}, {concurrency: 4});

The result follows ids order even when the 4 pending requests finish in another sequence.

Number normalized rows by input position use-input-index

const rows = await pMap(values, async (value, index) => ({
  row: index + 1,
  normalized: await normalize(value),
}), {concurrency: 8});

index tracks input order; version 7.0.5 corrected it for promised values that resolved out of order.

Save values from a paginated generator map-async-iterable

async function* pages() {
  let cursor;
  do {
    const page = await fetchPage(cursor);
    for (const item of page.items) yield item;
    cursor = page.nextCursor;
  } while (cursor);
}

const saved = await pMap(pages(), saveItem, {concurrency: 3});

concurrency:3 can produce overlapping next() calls, so the async iterator must tolerate them.

Bound completed records waiting on a database stream-results

import {pMapIterable} from 'p-map';

for await (const record of pMapIterable(ids, fetchRecord, {
  concurrency: 8,
  backpressure: 16,
})) {
  await database.insert(record);
}

backpressure:16 caps resolved, unconsumed records and cannot be smaller than concurrency:8.

Omit disabled profiles skip-results

import pMap, {pMapSkip} from 'p-map';

const active = await pMap(users, async user => {
  const profile = await loadProfile(user.id);
  return profile.disabled ? pMapSkip : profile;
}, {concurrency: 5});

pMapSkip removes an output instead of inserting undefined, shifting every later result index.

Report every failed upload collect-errors

try {
  await pMap(files, uploadFile, {
    concurrency: 3,
    stopOnError: false,
  });
} catch (error) {
  if (!(error instanceof AggregateError)) throw error;
  for (const failure of error.errors) console.error(failure);
}

stopOnError:false waits for all inputs and throws AggregateError; successful upload results are not returned.

Cancel mapping and fetch after 5 seconds abort-mapping

const controller = new AbortController();

const work = pMap(urls, async url => {
  const response = await fetch(url, {signal: controller.signal});
  return response.text();
}, {concurrency: 4, signal: controller.signal});

setTimeout(() => controller.abort(), 5_000);
await work;

The same signal reaches p-map and fetch because the outer option cannot interrupt a request already started.

Apply migrations sequentially run-serially

const results = await pMap(migrations, migration => migration.run(), {
  concurrency: 1,
});

concurrency:1 preserves sequence; a for-of loop is simpler if no mapped result array is needed.

Write thumbnails without collecting the batch limit-memory

for await (const thumbnail of pMapIterable(imagePaths, createThumbnail, {
  concurrency: 4,
  backpressure: 4,
})) {
  await writeThumbnail(thumbnail);
}

Matching backpressure:4 to concurrency:4 limits completed thumbnails waiting in memory.

Map values supplied as promises map-promised-inputs

const promisedIds = rawIds.map(id => Promise.resolve(Number(id)));
const records = await pMap(promisedIds, fetchRecord, {concurrency: 6});

p-map awaits each input before mapping it, but creating an eager promise can start work outside the concurrency:6 limit.

Attach a path to parse failures preserve-source-errors

const results = await pMap(paths, async path => {
  try {
    return await parseFile(path);
  } catch (error) {
    throw new Error(`Failed to parse ${path}`, {cause: error});
  }
}, {concurrency: 4});

p-map forwards mapper errors, so the mapper must attach the path needed to identify a failed input.

Start at most 5 requests per second combine-with-rate-limit

import pMap from 'p-map';
import pThrottle from 'p-throttle';

const throttledFetch = pThrottle({
  limit: 5,
  interval: 1_000,
  strict: true,
})(fetchRecord);

const records = await pMap(ids, throttledFetch, {concurrency: 2});

p-throttle enforces 5 starts per 1,000 ms; p-map separately caps pending requests at 2.

Alternatives

PackageRegistryPick it when
p-limitnpmChoose it when unrelated functions must share one concurrency counter
p-queuenpmChoose it for priorities, interval caps, pause, and queue lifecycle events
@supercharge/promise-poolnpmChoose it for a fluent pool API with per-item error handling

More utils guides

lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.