GitShow/sindresorhus/p-queue
sindresorhus

p-queue

Promise queue with concurrency control

by sindresorhus
async-functionsasync-queuenode-modulenpm-packagepromisepromise-queuequeuequeue-data-stucture
Star on GitHubForknpm

TypeScript

4.3k stars220 forks37 contributorsActive · 1mo agoSince 2016v9.3.3MIT

Meet the team

See all 37 on GitHub →
sindresorhus
sindresorhus136 contributions
Richienb
Richienb13 contributions
floatdrop
floatdrop9 contributions
Rafael09ED
Rafael09ED3 contributions
onury
onury2 contributions
ltetzlaff
ltetzlaff2 contributions
edorivai
edorivai2 contributions
dobesv
dobesv2 contributions

Languages

View on GitHub →
TypeScript100%

Commit activity

Last 12 weeks · 7 commits

Full graph →

Community health

4 of 6 standards met

Community profile →
85
✓README✓License✓Contributing✓Code of Conduct○Issue Template○PR Template

Recent PRs & issues

Active · 3 in progress · Last activity 1mo ago
See all on GitHub →
nolannguyen1212
`.add()` does not check `isPaused`, queue can grow unbounded after `.pause()`OpenIssue

stops the queue from running new tasks. It does not stop from accepting more work. In our system several independent call sites call . HTTP handlers, background pollers, etc. They don't know about each other. When we the queue intending to stop it, keeps succeeding from every one of those call sites. grows without bound. This caused a real memory leak for us. Minimal repro: is already public. So in theory every call site could check it before calling . In practice that means every call site has to remember to do this manually. It's easy to miss one. That's what happened here. It's also not atomic with the call. can run on another code path between the check and the call. So even a careful caller can still queue a task after the intent was to stop. We saw #239. We understand 's dangling-promise behavior is intentional, and that is the recommended cancellation primitive. We don't think solves this case though. It cancels tasks that were already added. It doesn't stop a producer from calling again in the first place. Is this the expected behavior? Is there a recommended pattern for stopping the queue from accepting new work, one that doesn't require every producer to remember to check themselves? Happy to send a small, additive PR if a guard along these lines would be welcome.

nolannguyen1212 · 2d ago
AmineYagoub
Proposal: `.remove(id)` — dequeue by id with settled promisesOpenIssue

Adds , the missing sibling of : tasks gained an and can be re-ranked, but nothing public can remove one — today removal requires threading an into every ahead of time. Semantics: Removes every QUEUED (not-yet-started) task whose matches; returns the count removed ( for an unknown id, no effect). Each removed task's promise rejects with the new exported (naming the id), and its function is never invoked — no forever-pending promises leaked. Running tasks are never touched; stopping started work remains the 's job (the doc says so). and size-derived observers (, , ) see the change immediately; , intervals, and concurrency bookkeeping are unaffected. Internally the removal settles through the same path as a queued-task abort, so there's no second bookkeeping route to drift. Includes node:test coverage (206 → 216 passing) for multi-match removal, unknown ids, promise settlement, running-task survival, and interplay, plus tsd type coverage and a readme entry beside . Full disclosure: this change was implemented end-to-end by Detent, an autonomous-engineering tool I'm building — every step gated by this repo's own test chain (xo, node:test, tsc, tsd) and independently reviewed before commit. Happy to adjust anything, or to close this in favor of an issue discussion if you prefer that flow. 🤖 Generated with Claude Code A complete implementation (206 → 216 tests green through xo/node:test/tsc/tsd) is ready on — GitHub's interaction limits currently prevent me from opening the PR directly; happy to open it the moment that clears, or for a maintainer to pull the branch.

AmineYagoub · 2d ago
Richienb
Require Node.js 26 and use native `EventTarget` instead of `EventEmitter`OpenPR

Completely works on Node.js 25. Will want to wait for Node.js 26 to be released and grow mature. Fixes #162

Richienb · 1mo ago

Recent fixes

View closed PRs →
vjymisal0
Allow changing intervalCap at runtimeMergedPR

Summary Adds a / accessor on , mirroring the existing accessor, so the rate limit can be adjusted after the queue has been created. Closes #177, which asks for exactly this ("The only thing required for this would be to control property of at runtime"). Changes is no longer ; added / with the same validation the constructor already does (must be a number >= 1, and must stay finite when is enabled). (an internal flag derived from /) is now recomputed via a small helper whenever changes, so it doesn't go stale. Setting calls the existing , so raising the cap immediately lets already-queued tasks start rather than waiting for the next interval tick (same behavior as the existing setter). README: documented that can be changed at runtime, and added the entry to the properties list (same pattern as ). Testing Added (mirrors the existing test). Added (mirrors the constructor's equivalent guard). Added , which proves the behavior end-to-end: with , only 1 of 4 queued tasks starts; raising to immediately starts 2 more without waiting for an interval tick. Verified RED/GREEN: reverting just makes the three new tests fail ( is currently read-only), confirming they exercise the reported gap; with the fix applied, passes all 209 tests. and both pass clean with no errors. currently fails on this checkout with "was not found by the project service" parsing errors on and everything under / — verified this is pre-existing and unrelated to this diff (same errors occur on a clean checkout of with no changes applied), likely an environment/path quirk in this sandbox rather than a real lint issue. Notes itself is intentionally left read-only in this PR to keep the change small — it drives timer/window scheduling (/) which needs more careful handling to change safely mid-window. Happy to follow up separately if that's wanted too.

vjymisal0 · 1w ago
tsushanth
Fixed-window rate limiter incorrectly delays tasks in a new interval when `intervalCap > 1` and queue went idle mid-windowClosedIssue

Version:* 9.3.2 Behaviour When and tasks are spread across a window (first slot consumed early, last slot consumed later), tasks added after* the interval window expires are incorrectly held back — even though they belong to a fresh window. Reproduction Output: Root cause The fix for #182 introduced a guard inside that, when the interval timer is absent and has already passed, checks whether at least ms have elapsed since the most-recently-dequeued task: `` the last dequeued slot can be significantly later than the window's start, so overshoots the true window boundary. In the repro above: Window started at t≈0, so it expires at t=1000. Last task was dequeued at t≈600, so . Task C arrives at t=1100 (after true window expiry) but , so a 500 ms timeout is created and is never reset. Task C is blocked until t≈1600 even though it is in window [1000, 2000) and both slots are free. Expected behaviour A task added after has passed should experience no additional delay regardless of when within the previous window the last slot was consumed. The window boundary () already captures this correctly; the check should not extend it.

tsushanth · 1mo ago
tsushanth
Per-task `timeout` in `add()` skips validation that the constructor enforcesClosedIssue

Bug The constructor validates the global option and throws a clear for non-positive values. But accepts per-task without any equivalent check. This leads to two silent failure modes: Case 1 — silently disables the global timeout The guard inside is: \\\ is falsy, so bypasses the wrapping entirely. If a queue was constructed with , passing to a specific task silently drops the timeout for that task. There's no error and no documentation of this escape hatch. Case 2 — produces a cryptic internal error validates its argument and throws synchronously when it's not strictly positive. That TypeError propagates out of as an unhandled rejection — but the message names internals, not the call-site, making it hard to trace. Expected Both cases should throw a clear at the call-site, consistent with the constructor: \\timeout0\ Suggested fix After merging options in , add: \\Expected \ to be a positive finite number, got \\ And change the guard from to . Repro \\\

tsushanth · 1mo ago
Structured data for AI agents

Repository: sindresorhus/p-queue. Description: Promise queue with concurrency control Stars: 4264, Forks: 220. Primary language: TypeScript. Languages: TypeScript (100%). License: MIT. Topics: async-functions, async-queue, node-module, npm-package, promise, promise-queue, queue, queue-data-stucture. Latest release: v9.3.3 (1mo ago). Open PRs: 3, open issues: 4. Last activity: 1mo ago. Community health: 85%. Top contributors: sindresorhus, Richienb, floatdrop, Rafael09ED, onury, ltetzlaff, edorivai, dobesv, BendingBender, bobjflong and others.

·@ofershap

Replace github.com with gitshow.dev