p-queue
Promise queue with concurrency control, interval-based rate limiting, and priority scheduling for async tasks.
Repository Health
Technical Analysis
p-queue is a promise queue for Node.js and modern browsers that runs async (or sync) functions with a configurable concurrency limit. Instead of firing every task at once, callers .add() work to the queue and p-queue dequeues it as capacity frees up, making it a common building block for rate-limiting calls to REST APIs, throttling CPU/memory-intensive jobs, or capping parallel file operations.
Beyond a basic concurrency cap, it supports interval-based throttling (intervalCap + interval, with an optional sliding-window strict mode), per-task priority so higher-priority work jumps the queue, per-task or per-queue timeouts via p-timeout, and AbortSignal cancellation. It exposes an EventEmitter3-based event surface (active, idle, empty, next, completed, error, rateLimit, and more) plus promise-returning helpers like .onIdle(), .onEmpty(), and .onSizeLessThan() for coordinating queue state without polling.
What You Get
- A
PQueueclass you instantiate withconcurrency,intervalCap/interval,timeout, andautoStartoptions, then feed work to via.add()or.addAll() - A pluggable
queueClass— the defaultPriorityQueuekeeps a sorted, amortized-O(1) array so higher-prioritytasks run first, and you can swap in your own class implementingenqueue/dequeue/size - Interval-based throttling with a fixed-window default and an opt-in
strictsliding-window mode that tracks execution timestamps to prevent bursts at window boundaries - Promise-returning state hooks —
.onEmpty(),.onIdle(),.onPendingZero(),.onRateLimit(),.onRateLimitCleared(),.onSizeLessThan(),.onError()— so you canawaitqueue transitions instead of listening for events - Per-task cancellation via
AbortSignaland per-task or per-queuetimeout(backed byp-timeout, raisingTimeoutError) without writing your own timer bookkeeping
Common Use Cases
- Rate-limiting calls to a third-party REST API that enforces a requests-per-second cap
- Throttling CPU- or memory-intensive tasks (image processing, file I/O) to a fixed number of parallel workers
- Reordering in-flight work by priority — e.g. serving interactive requests ahead of background batch jobs sharing the same worker pool
- Coordinating multiple producers adding work over time by awaiting
.onIdle()or.onEmpty()instead of managing a manual counter
Under The Hood
Architecture
Execution flows from a single public entry point, PQueue in source/index.ts, which owns all scheduling state (concurrency counters, interval timers, pending-task bookkeeping) and delegates ordering to an injected queue implementation via the queueClass option — source/priority-queue.ts by default. This separation of concerns means the class enforces concurrency, timeout, and rate-limit invariants directly, while pluggable, encapsulated logic (implementing the small Queue interface from source/queue.ts) decides what runs next; swapping queueClass changes ordering strategy without touching any scheduling code. Interval-based rate limiting runs as a small state machine of its own (#initializeIntervalIfNeeded, #onInterval, recurring vs one-shot timers) layered on top of the same dequeue loop the plain-concurrency path uses, so strict sliding-window mode extends rather than forks the core scheduling logic. The design is intentionally a flat orchestrator plus one swappable strategy object, not a deep layered architecture — reasonable for a focused primitive, and it means the core abstraction that would break the most if changed is the Queue interface’s contract (enqueue/dequeue/size/setPriority/remove), since every scheduling decision funnels through it.
Tech Stack
Written in strict TypeScript targeting Node.js 20+, compiled with tsc against @sindresorhus/tsconfig, and shipped as ESM-only (no CommonJS export, "type": "module"). Runtime dependencies are minimal and purposeful: eventemitter3 supplies the typed event-emitter base class PQueue extends, and p-timeout implements the per-task/per-queue timeout wrapping (including its TimeoutError). There is no build framework beyond tsc plus del-cli for cleaning dist/; the package ships only compiled dist/ output per its files field. CI (.github/workflows) runs the test suite via npm test across a Node 20/24 matrix on GitHub Actions.
Code Quality
Testing uses Node’s built-in node:test runner (via tsx/esm) across seven dedicated files (basic.ts, advanced.ts, strict.ts, rate-limit.ts, priority-queue.ts, validation.ts, debug.ts) totaling well over 100KB of test code, exercising concurrency edge cases, timing-sensitive rate-limit behavior, priority ordering, and input validation with real timers and assert/strict. Linting is enforced through xo (a strict, opinionated ESLint preset) with only a small, explicitly justified set of TypeScript rule overrides, and tsd runs type-level tests against the public .d.ts output in test-d/ to guard the published type signatures themselves, not just runtime behavior. Source code favors private class fields (#), explicit TypeError throws with descriptive messages for invalid options, and inline comments explaining non-obvious invariants (e.g. the circular-buffer bookkeeping in #cleanupStrictTicks) rather than leaving them undocumented.
API Design
The public surface is a single constructor plus a small, consistently-named method set (.add(), .addAll(), .pause(), .start(), .clear()) paired with a family of on*() promise-returning state hooks (.onIdle(), .onEmpty(), .onPendingZero(), .onRateLimit(), .onSizeLessThan()) that let callers await queue transitions instead of wiring up event listeners by hand, while the underlying EventEmitter3 events remain available for repeated-notification cases. Getting started requires no boilerplate beyond new PQueue(options) and .add(fn); every option (concurrency, intervalCap, interval, timeout, priority) is optional with a sensible default, and JSDoc on each option in source/options.ts includes inline usage examples. The README documents every corner (custom queueClass, AbortSignal cancellation, setPriority) with runnable code samples, keeping the learning curve low despite the library’s fairly deep feature set.
Used by 22 apps in this directory
likec4
Developer Tools · Devops
Define your software architecture as code and get always up-to-date, interactive C4 diagrams generated automatically from a DSL.
Logto
Authentication
Open-source auth infrastructure for SaaS and AI apps with OIDC, SAML, and RBAC
Mattermost
Collaboration · Devops · Team Chat
Open core, self-hosted team collaboration with chat, AI agents, voice calling, and deep DevOps integrations — all under your control.
nango
Authentication · Automation · Developer Tools
Build product integrations with AI using 800+ APIs — auth, proxy, and TypeScript functions on production-grade infrastructure.
NocoBase
Low Code Platforms · No Code Platforms
Open-source AI + no-code platform that lets coding agents and people collaborate to build business systems fast on proven infrastructure.
NocoDB
Databases · Low Code Platforms · No Code Platforms
Turn any SQL database into a collaborative no-code spreadsheet with automatic REST APIs and real-time views.
Notesnook
File Storage · Note Taking · Security
End-to-end encrypted, open-source note-taking where your data stays yours — even from the server.
Novu
Developer Tools
Open-source communication infrastructure that connects your products and AI agents to every channel your users live on — Inbox, Email, SMS, Push, Chat, and more.
overleaf
Collaboration · Productivity
Open-source, real-time collaborative LaTeX editor with sandboxed compilation and full TeXLive support for self-hosted academic and research teams.