mrkeyoor.com_
Wed 23 Sept 02:54 UTC
npmUtilsupdated 22 Sept 2026

promise-limit review

promise-limit 2.7.0 is a small FIFO gate for functions that return promises. Create a limiter with a concurrency count, submit zero-argument functions, and it starts only that many at once. The returned function also has map() for array work and queue for the number of jobs waiting to start. Our browser build was 1.3 KB minified and 0.6 KB gzipped. There are no runtime dependencies, priorities, time windows, cancellation hooks, or queue-clearing controls.

Verdict

promise-limit 2.7.0 installed in 0.5 seconds as 1 package, and our bundle measured 0.6 KB gzipped with 0 audit findings. It remains reasonable for a frozen CommonJS service that needs FIFO concurrency only; new code should prefer p-limit when cancellation-adjacent queue controls and current maintenance matter.

We installed it

Lab card: what happened when we installed promise-limitScreenshot of promise-limit documentation
Install✓ · 0.5s1 package on disk · 1 MB
ImportESM import works · require() works · CommonJS package
Browser0.6 KBgzipped (1.3 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does promise-limit install cleanly?

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

How much does promise-limit add to a browser bundle?

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

Does promise-limit work with both ESM and CommonJS?

Yes. Both import 'promise-limit' and require('promise-limit') worked in Node 22 in our run. The package is published as CommonJS.

Does promise-limit include TypeScript types?

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

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

p-limit: Use it for active and pending counts, queue clearing, mutable concurrency, and a currently maintained API. promise-limit 2.7.0 installed in 0.5 seconds as 1 package, and our bundle measured 0.6 KB gzipped with 0 audit findings.

When should you not use promise-limit?

Cancellation, timeouts, priorities, pause, rate windows, or clearing pending work are requirements; version 2.7.0 implements none of them.

API stability5/5Version 2.7.0 consists of one factory, a callable limiter, map(), and the queue number. The README matches the implementation on FIFO starts, zero meaning unlimited, and first-error map behavior. Our Node 22.23.2 checks loaded it through require() and ESM import. No runtime dependencies can change its behavior underneath a lockfile, although this consistency comes from a package that has not released since 2018.
Docs3/5The README has a complete runnable example and defines the factory, wrapper, map helper, queue counter, ordering, and rejection behavior. It does not call out that map() invokes .catch directly, distinguish waiting from active work with an example, or cover producer backpressure and shutdown. Bundled declarations help TypeScript users inspect the signatures, but there is no deeper reference or migration guide.
Maintenance1/5npm published version 2.7.0 in July 2018, and GitHub records the latest push on July 18, 2022. The repository is unarchived and reports 10 open issues and pull requests, while its package metadata still uses Travis-era development tooling. A 56 KB dependency-free package can be finished, but there is little current stewardship evidence if a new Node behavior exposes a defect.
Ecosystem3/5npm counted 4,279,647 downloads in the week ending August 24, 2026, and the repository has 143 stars. Zero runtime dependencies and working CommonJS-to-ESM interop make it easy to inherit. The surrounding ecosystem is narrow: no framework adapters, distributed limiter, cancellation protocol, or rate-limit plugins exist, and current examples more often target p-limit.

Use it if

  • A CommonJS service needs one local concurrency cap around promise-returning calls.
  • Tasks should begin in submission order and results can be collected with Promise.all.
  • A stream producer can use the exposed waiting count to apply its own backpressure.
  • You need a 0.6 KB gzipped limiter and the package's fixed API already covers the job.
Skip it if

Setup reality

We installed promise-limit 2.7.0 in a fresh Node 22 Bookworm sandbox in 0.5 seconds. The install left 1 package and 1 MB on disk, and npm audit found 0 known vulnerabilities. The package has 0 direct dependencies, 0 peer dependencies, 56 KB unpacked, an ISC license, and bundled TypeScript declarations. It is CommonJS without an exports map; require() and ESM import both worked under Node 22.23.2.

There are no credentials, config files, install scripts, or native modules. Give the limiter a function that starts the operation, such as limit(() => fetch(url)). Passing fetch(url) starts the request before the limiter sees it. Functions receive no arguments, so use a closure. Synchronous throws become rejected promises and release their slot.

Jobs start in the order submitted, while completion order depends on the work. queue counts waiting jobs only and excludes active jobs. The package does not bound that queue, pause a producer, expose activeCount, or provide shutdown. A fast input stream can still fill memory unless your code stops reading when queue crosses its own threshold.

The map() helper has a sharper failure contract than the basic limiter. Its mapper must return a real promise because version 2.7.0 calls .catch directly. After the first rejection it prevents queued mapper functions from running, although already-started calls continue. Our browser measurement was 1.3 KB minified and 0.6 KB gzipped, so size is not the reason to reject it; missing controls and old maintenance are.

Patterns

Cap parallel requests at 3 limit-concurrency

const promiseLimit = require('promise-limit');
const limit = promiseLimit(3);

const results = await Promise.all(
  urls.map((url) => limit(() => fetchJson(url)))
);

Version 2.7.0 needs a function that starts work; passing an already-created promise bypasses the 3-task cap.

Start jobs one at a time run-serially

const limit = require('promise-limit')(1);

await Promise.all([
  limit(() => writeRecord('first')),
  limit(() => writeRecord('second')),
  limit(() => writeRecord('third')),
]);

A concurrency of 1 preserves submission order, while Promise.all still rejects as soon as one returned promise rejects.

Capture arguments in the task closure pass-arguments

const limit = require('promise-limit')(4);

function lookup(id, locale) {
  return limit(() => fetchUser(id, locale));
}

The limiter invokes each function with 0 arguments, so the closure must carry id and locale.

Map with a fixed concurrency map-array

const limit = require('promise-limit')(5);

const pages = await limit.map(ids, (id, index) =>
  fetchPage(id).then((page) => ({index, page}))
);

The mapper must return a promise in version 2.7.0 because map() calls .catch on its result.

Keep output aligned with input preserve-result-order

const inputs = ['slow', 'fast', 'medium'];
const limit = require('promise-limit')(2);
const outputs = await Promise.all(
  inputs.map((value) => limit(() => transform(value)))
);

Promise.all returns values in input order even when 2 tasks finish in the opposite order.

Recover from one limited failure handle-task-error

const limit = require('promise-limit')(3);
const value = await limit(() => callService()).catch((error) => {
  report(error);
  return null;
});

A rejection releases its slot before the returned promise rejects, allowing later queued work to start.

Treat a synchronous throw as rejection convert-sync-throw

await limit(() => {
  if (!input.id) throw new Error('missing id');
  return save(input);
});

The implementation catches a synchronous exception, decrements the active count, and returns a rejected promise.

Watch jobs waiting to start inspect-waiting-count

for (const item of items) {
  pending.push(limit(() => processItem(item)));
  if (limit.queue > 100) await pauseProducer();
}
await Promise.all(pending);

queue reports waiting jobs only; add the configured concurrency to estimate waiting plus running work.

Slow an async producer add-backpressure

for await (const record of source) {
  while (limit.queue >= 50) {
    await new Promise((resolve) => setTimeout(resolve, 25));
  }
  void limit(() => save(record)).catch(reportError);
}

Version 2.7.0 does not pause the source automatically, so this 50-job threshold belongs to application code.

Select pass-through mode disable-the-cap

const limit = require('promise-limit')(shouldThrottle ? 8 : 0);
await Promise.all(tasks.map((task) => limit(task)));

0 selects unlimited execution; it does not create a limiter with 0 available slots.

Handle map's early stop stop-map-after-error

try {
  await limit.map(records, (record) => validateAndSave(record));
} catch (error) {
  console.error('mapping stopped', error);
}

Queued mapper calls are skipped after the first rejection, but tasks among the active 2 or 3 cannot be cancelled.

Run every task despite failures collect-all-results

const outcomes = await Promise.all(jobs.map((job) =>
  limit(async () => {
    try { return {status: 'fulfilled', value: await run(job)}; }
    catch (reason) { return {status: 'rejected', reason}; }
  })
));

Wrap each task's error when all jobs must run; map() deliberately stops scheduling after its first rejected promise.

Alternatives

PackageRegistryPick it when
p-limitnpmUse it for active and pending counts, queue clearing, mutable concurrency, and a currently maintained API.
bottlenecknpmUse it for rate windows, priorities, reservoirs, retries, or coordination across processes.
async-semanpmUse it when explicit acquire and release semantics fit resource pools or stream backpressure.

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.