Library / SDK
sindresorhus/p-limit avatar
sindresorhus/p-limit

p-limit: clearing the queue does not cancel what is already running

Run multiple promise-returning & async functions with limited concurrency

2,941 stars148 forksJavaScriptMIT

At a glance

What is it?
p-limit is one function that caps how many promise-returning functions run at once, with a single small runtime dependency and a documentation page that is unusually candid about where it stops. Three of its documented behaviours are hazards rather than features, and it is better to know them before the teardown path than after.
Who is it for?
p-limit is the right tool when the only thing you need is a ceiling on concurrent executions, and the page says so plainly by positioning itself as the small option next to a full queue implementation. It is the wrong tool when you need cancellation, since clearQueue discards waiting work and does nothing to work already in flight, and the documented escape is a flag that rejects the pending promises rather than stopping them.
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 56 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 November 13, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • sindresorhus/p-limit2,941 ★ · +14
Star history
Repository2026-09-242026-11-13
sindresorhus/p-limit2.9k2.9k

14 Stars in the last 50 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

Using the same limiter inside work it already limits deadlocks it

One warning in the API reference is worth more than the rest of the option documentation put together. Avoid calling the same limit function inside a function that is already limited by it, because this can create a deadlock where inner tasks never run, and the instruction is to use a separate limiter for inner tasks. The mechanism follows from the properties the page defines. The concurrency value has a minimum of 1 and caps how many promises run at once, activeCount counts the promises currently running, and pendingCount counts promises whose internal function has not been called yet. So if the single free slot is held by a task that is itself waiting on another call to the same limiter, nothing will ever start to satisfy that wait. Nothing throws, nothing logs, and the queue simply stops draining. Two limiters is the whole fix, and the page states it in one sentence with no example.

clearQueue drops waiting work and leaves running work alone

The teardown method has a stated scope and an unstated default. limit.clearQueue() discards pending promises that are waiting to run, and the page suggests it for tearing the queue down at the end of a program's lifecycle or for discarding function calls that reference an intermediary state of your app. Then the note: this does not cancel promises that are already running. There is no abort signal, no cancellation token and no timeout in the API, so a task that has already started runs to completion whatever you do to the queue. What happens to the discarded promises depends on a flag. rejectOnClear is a boolean that defaults to false, and when it is enabled, pending promises are rejected with an AbortError. The page recommends enabling it if you await the returned promises, for example with Promise.all, so pending tasks do not remain unresolved after clearQueue(). Left at its default, the promises you were going to await never settle.

limit.map keeps running scheduled mappers after the iterable throws

The batch helper documents an error path with a consequence that is easy to skim past. If the iterable throws during iteration, the returned promise rejects with that error. Mapper functions that were already scheduled still run, and their results are discarded. So the rejection you catch is not the end of the work: every mapper the limiter had already dequeued goes on to execute, and only the values are thrown away. For mappers that are pure reads that is harmless. For mappers that write, send a request or charge something, an iterable that fails on the tenth item still leaves the first nine side effects in place with no result to show for them. The return value is documented as equivalent to Promise.all(Array.from(iterable, (item, index) => limit(mapperFunction, item, index))), and the mapper receives the item value and its index. The page positions this as a convenience for inputs arriving in batches and points to p-map for more complex cases.

The exports map declares no CommonJS branch and the engines field says Node 20

The manifest is small and three fields decide who can install it. The type field is module, so the package is ES modules only. The exports map has exactly two keys, types pointing at ./index.d.ts and default pointing at ./index.js, with no require condition and no nested entry points. The engines field requires Node 20 or newer. Taken together those three mean there is no CommonJS entry point to require, and a project still on CommonJS has to reach this package through dynamic import. The published payload matches the exports map: the files array lists index.js and index.d.ts and nothing else, even though the repository also contains readme.md, recipes.md, test.js, benchmark.js, index.test-d.ts, a licence file and a scripts directory. The runtime dependency list has one entry, yocto-queue at ^1.2.1, which is the queue the limiter is built on, and sideEffects is false so a bundler can drop an unused import.

concurrency is readable and writable after the limiter is built

The limiter is not frozen at construction. limit.concurrency is documented as get or set the concurrency limit, so the ceiling can be changed on a live object, which means it can be lowered below the number of tasks currently running as easily as raised. The two counters beside it are introspection rather than control: activeCount is the number of promises currently running, and pendingCount is the number of promises waiting to run, defined precisely as those whose internal function has not been called yet. That distinction matters when reading the numbers. A task that has been dequeued and whose function has been entered is active, so pendingCount can read zero while the queue is still working through everything already admitted. The construction value itself is more permissive than the type signature suggests: concurrency takes a number or an object with a concurrency property, so the options object form also carries rejectOnClear.

Two exports with opposite shapes: one you pass work to, one that owns its own limiter

The default export and the named export are two different arrangements of the same idea, and the page explains when to pick which. pLimit(concurrency) returns a limit function, which you then call with a function and any arguments, and it returns the promise that function returns. The shape of that call is the whole usage example:

js
import pLimit from 'p-limit';

const limit = pLimit(1);

const input = [
	limit(() => fetchSomething('foo')),
	limit(() => fetchSomething('bar')),
	limit(() => doSomething())
];

// Only one promise is run at once
const result = await Promise.all(input);
console.log(result);

Notice that the example passes thunks rather than functions with arguments, which is what the separate arguments parameter is for: it exists so callers can avoid creating closures, with the honest caveat that you probably do not need this optimization unless you are pushing a lot of functions. limitFunction(fn, options) is the other arrangement. It returns a function with limited concurrency that manages its own concurrent executions, so you can call it many times without exceeding the limit, and the page frames it as ideal for controlling the number of simultaneous executions of a single function rather than concurrency across multiple functions. Its example builds a limited function with a concurrency of 1 and hands it straight to Array.from over ten items before awaiting them all.

The only comparison on the page is p-limit against p-queue, made by subtraction

The FAQ has one entry and it is a positioning statement rather than a feature table. Asked how this differs from p-queue, the answer is that this package is only about limiting the number of concurrent executions, while p-queue is a fully featured queue implementation with lots of different options, introspection, and ability to pause the queue. So the differentiator is scope: a concurrency limiter rather than a full queue with options, introspection and pausing. Everything else in the readme points the same way. The Related section lists p-throttle for throttling, p-debounce for debouncing, p-map for running with different inputs, and p-all for running concurrently with optional limited concurrency, and limit.map itself defers to p-map for complex cases. No other package is compared on any axis, and no benchmark result is quoted anywhere.

Editorial conclusion

p-limit is the right tool when the only thing you need is a ceiling on concurrent executions, and the page says so plainly by positioning itself as the small option next to a full queue implementation. It is the wrong tool when you need cancellation, since clearQueue discards waiting work and does nothing to work already in flight, and the documented escape is a flag that rejects the pending promises rather than stopping them. Before you wire it into a shutdown path, decide whether your tasks are safe to leave running, because that is the assumption the library makes on your behalf. Read the deadlock warning before nesting, keep a separate limiter for inner work, and check whether the mapper side effects in limit.map matter to you when the input iterable throws partway through.

Frequently asked questions

What is the p-limit?

It is an npm package that runs multiple promise-returning and async functions with limited concurrency, working in Node.js and browsers. It exports a default function that takes a concurrency value, has a minimum of 1, and returns a limit function you call with a function and its arguments.

p limit vs p queue

The package answers this itself. It is only about limiting the number of concurrent executions, while p-queue is a fully featured queue implementation with lots of different options, introspection, and the ability to pause the queue.

Can I require p-limit from CommonJS?

Not through the package's own entry points. The manifest sets type to module and its exports map contains only types and default, pointing at index.d.ts and index.js, with no require condition, and the engines field requires Node 20 or newer. Install it with npm install p-limit.

Does clearQueue cancel tasks that are already running?

No. clearQueue discards pending promises that are waiting to run and does not cancel promises that are already running. With rejectOnClear enabled the pending promises are rejected with an AbortError, which is recommended when you await them with Promise.all; otherwise they remain unresolved.

What happens to limit.map when the iterable throws mid-iteration?

The returned promise rejects with that error, but mapper functions that were already scheduled still run and their results are discarded. The page points to p-map for more complex use cases.

What is a p limit in a ventilator?

This repository has nothing to say about ventilators. It is a JavaScript package for capping concurrent executions of promise-returning functions, and the only thing named p-limit in its documentation is the package itself and its limiter function.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. sindresorhus/p-limit on GitHub
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/sindresorhus-p-limit.svg)](https://hysenlabs.com/projects/sindresorhus-p-limit)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/sindresorhus-p-limit.svg)](https://hysenlabs.com/projects/sindresorhus-p-limit)

Was this useful?