mrkeyoor.com_
Sat 08 Aug 22:01 UTC
npmUtilsupdated 08 Aug 2026

promise-limit

promise-limit is a tiny CommonJS semaphore for promise-returning work. Create one limiter with a concurrency count, pass it zero-argument functions, and it starts no more than that count at once while preserving submission order. It also includes a limited map helper and exposes the number of waiting jobs. There are no runtime dependencies, no scheduler configuration, and no cancellation layer.

Verdict

A readable, dependency-free limiter for legacy CommonJS code, but its frozen feature set and missing types make p-limit the better default for new work. Keep it when four tiny methods are exactly the contract you need.

API stability5/5The entire version 2.7.0 surface is one factory, the returned limiter function, limit.map, and limit.queue. The README and implementation agree on those behaviors, and no runtime dependencies can shift underneath them. That narrow design has not changed since the repository's final 2022 push, though stability here partly reflects inactivity rather than an explicit compatibility policy.
Docs3/5The README clearly explains the constructor, wrapper function, map helper, queue counter, ordering, and first-error behavior, with one runnable concurrency example. It does not document module interop, synchronous throws, the mapper's direct .catch assumption, TypeScript usage, cancellation, or production backpressure patterns, and there is no separate documentation site.
Maintenance2/5The repository is not archived and npm does not mark the package deprecated, but version 2.7.0 and the last GitHub push both date to 2022. The repository currently reports 10 open issues and pull requests, while its development dependencies still reference Travis-era tooling. This is plausible for a finished utility, but there is little evidence of current stewardship.
Ecosystem3/5The package recorded 3,879,745 downloads for the measured week and has no runtime dependencies, so it is clearly entrenched in dependency trees. Its own ecosystem is small, however: there are no bundled TypeScript declarations, adapters, framework integrations, or extension hooks, and the GitHub repository has 143 stars. Most new examples and companion tooling center on p-limit instead.

Use it if

  • You maintain CommonJS code and need a minimal concurrency gate around an existing promise-returning function
  • You want queued tasks to start in the same order they were submitted
  • You need a simple queue-length signal so a producer can pause before pending work consumes too much memory
  • You support older Node.js code where a dependency-free, pre-ESM limiter is easier to introduce than a modern ESM-only package
Skip it if

Setup reality

Installation is only npm install promise-limit, and version 2.7.0 has no runtime dependencies, native build, peer dependency, credentials, or config file. The package is CommonJS, so the documented shape is const promiseLimit = require('promise-limit'); TypeScript users must supply their own declaration or install a community declaration if one fits. The limiter accepts functions, not already-started promises. Calling limit(fetch(url)) starts the work before the limiter can control it and passes the wrong value; wrap it as limit(() => fetch(url)). A count of 0 or undefined means unlimited execution rather than a stopped queue. There is no input validation, timeout, AbortSignal support, priority, queue clearing, or explicit shutdown. A task releases its slot when its returned promise settles, and synchronous throws are converted to rejections. limit.map has a sharper edge: the mapper is expected to return an actual promise because the implementation calls .catch on it, and after one mapper rejects, work still waiting in the queue is skipped. The queue property counts waiting jobs only, not running jobs. If producers can outrun consumers, you must watch that value and apply backpressure yourself.

Patterns

Run at most three jobs at oncelimit-concurrency

const promiseLimit = require('promise-limit')

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

Pass a function that starts the work. Passing an existing promise cannot prevent that promise from already running.

Serialize promise-returning tasksrun-serially

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

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

A concurrency of 1 starts tasks in submission order, but Promise.all still rejects as soon as any submitted task rejects.

Capture task arguments in a closurepass-arguments

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

function limitedLookup(id, locale) {
  return limit(() => lookupUser(id, locale))
}

const user = await limitedLookup(42, 'en')

The limiter calls its function with no arguments, so close over every value the task needs.

Map an array with bounded concurrencymap-with-limit

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

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

The mapper must return a promise. The implementation calls .catch on its result rather than normalizing a plain value first.

Keep results aligned with inputspreserve-result-order

const limit = require('promise-limit')(2)
const inputs = ['slow', 'fast', 'medium']

const outputs = await Promise.all(
  inputs.map((value) => limit(() => transform(value)))
)
// outputs use input order, even if completion order differs

Promise.all preserves the order of its input promises; the limiter also starts queued work in submission order.

Handle each limited task failurehandle-task-errors

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

const result = await limit(() => callService()).catch((error) => {
  console.error('service call failed', error)
  return null
})

A rejected task releases its slot before the returned promise rejects, so later queued work can continue.

Let a synchronous throw become a rejectionconvert-sync-throw

const limit = require('promise-limit')(2)

try {
  await limit(() => {
    throw new Error('bad input')
  })
} catch (error) {
  console.error(error.message)
}

The implementation catches synchronous exceptions and returns a rejected promise while correctly releasing the slot.

Inspect the number of waiting jobsobserve-queue

const limit = require('promise-limit')(2)

for (const item of items) {
  pending.push(limit(() => processItem(item)))
  if (limit.queue > 100) console.warn('producer is outrunning workers')
}
await Promise.all(pending)

limit.queue excludes currently running jobs and only reports tasks that have not started.

Pause a producer when the queue growsapply-backpressure

const limit = require('promise-limit')(4)
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))

for await (const record of source) {
  while (limit.queue >= 50) await sleep(25)
  void limit(() => save(record)).catch(reportError)
}

The package does not cap its queue or pause producers automatically; this polling is application-level backpressure.

Select unlimited mode explicitlydisable-limit

const promiseLimit = require('promise-limit')
const limit = promiseLimit(shouldThrottle ? 8 : 0)

await Promise.all(tasks.map((task) => limit(task)))

Both 0 and undefined select the pass-through implementation. They do not create a queue with zero available slots.

Understand map failure behaviorstop-map-on-error

const limit = require('promise-limit')(2)

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

After the first rejection, limit.map rejects and queued mapper calls are skipped. Already-running calls cannot be cancelled.

Collect outcomes without limit.map stopping earlycollect-all-outcomes

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

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 errors inside each task when every job must run; limit.map intentionally stops scheduling mapper calls after a failure.

Alternatives

PackageRegistryPick it when
p-limitnpmModern projects that want TypeScript declarations, active maintenance, mutable concurrency, and queue controls
p-mapnpmArray mapping with concurrency, AbortSignal support, and configurable stop-on-error behavior
bottlenecknpmAPI clients that need rate windows, priorities, reservoirs, retries, or distributed coordination