RepoPilot

Show me how the concurrency limiter actually queues tasks.

About sindresorhus/p-limit · 2 messages · last activity 7/20/2026

Question

Show me how the concurrency limiter actually queues tasks.

RepoPilot

Here's the queuing mechanism, all implemented in index.js:

1. The queue itself

A Queue instance backs the limiter, and activeCount tracks how many tasks are currently running [index.js:L3-L16]:

const queue = new Queue();
let activeCount = 0;

2. Enqueueing a task

Every call to limit(fn, ...args) goes through generator, which wraps the call in a new Promise and immediately calls enqueue [index.js:L64-L67]:

const generator = (function_, ...arguments_) => new Promise((resolve, reject) => {
	enqueue(function_, resolve, reject, arguments_);
});

enqueue doesn't push the actual task function onto the queue — it pushes a queueItem whose run property is the resolve function of an inner promise. This inner promise, once resolved (i.e. "released" to run), triggers run(function_, resolve, arguments_) via .then [index.js:L46-L60]:

const enqueue = (function_, resolve, reject, arguments_) => {
	const queueItem = {reject};

	new Promise(internalResolve => {
		queueItem.run = internalResolve;
		queue.enqueue(queueItem);
	}).then(run.bind(undefined, function_, resolve, arguments_));

	if (activeCount < concurrency) {
		resumeNext();
	}
};

This indirection (queuing the internalResolve function rather than the task directly) is explicitly to preserve the async execution context across the queue boundary — noted in the comment at [index.js:L49-L50].

3. Releasing queued tasks

resumeNext is the gatekeeper — it only dequeues and starts a task if under the concurrency cap [index.js:L18-L23]:

const resumeNext = () => {
	if (activeCount < concurrency && queue.size > 0) {
		activeCount++;
		queue.dequeue().run();
	}
};

Calling .run() on the dequeued item resolves that inner promise, which unblocks the .then(run.bind(...)) chain set up in enqueue.

4. Actually executing the task

run invokes the real function, resolves the outer promise immediately with the resulting promise (so the caller isn't blocked waiting for completion), then awaits it just to know when to free up a concurrency slot [index.js:L27-L41]:

const run = async (function_, resolve, arguments_) => {
	const result = (async () => function_(...arguments_))();
	resolve(result);
	try {
		await result;
	} catch {}
	next();
};

5. Freeing a slot and cascading

When a task finishes, next decrements activeCount and calls resumeNext again, continuing the chain until the queue is empty or the limit is hit [index.js:L25-L28]:

const next = () => {
	activeCount--;
	resumeNext();
};

6. Dynamic concurrency changes

Setting limit.concurrency = n revalidates the value, then schedules a microtask that drains the queue up to the new limit [index.js:L92-L104]:

set(newConcurrency) {
	validateConcurrency(newConcurrency);
	concurrency = newConcurrency;
	queueMicrotask(() => {
		while (activeCount < concurrency && queue.size > 0) {
			resumeNext();
		}
	});
},

This is exercised in tests like "change concurrency to smaller/bigger value" [test.js:L305-L339], which confirm that lowering concurrency mid-flight throttles new starts while running tasks continue, and raising it immediately drains more of the queue.

Supporting pieces

  • activeCount and pendingCount (= queue.size) are exposed as read-only getters on the returned generator function [index.js:L69-L74].
  • clearQueue() empties the queue, optionally rejecting pending items with an AbortError if rejectOnClear was set [index.js:L75-L86], as also documented in the README [readme.md:L107-L110] and demonstrated in the graceful-shutdown recipe.

Per the architecture signals, index.js is the most-depended-on file in the repo (3 importers) and the whole graph has 0 import cycles with an A-grade (100/100) score, so this queuing logic

Want to ask your own question?

Open chat for sindresorhus/p-limit

Public Q&A. Generated by RepoPilot from the actual source of sindresorhus/p-limit. AI answers can be incomplete or stale — verify before relying on them.