RepoPilot

Show me how the concurrency limiter actually queues tasks.

About sindresorhus/p-limit · 2 messages · last activity 5/18/2026

Question

Show me how the concurrency limiter actually queues tasks.

RepoPilot

Great! I found that p-limit uses an external dependency. Let me explain how the task queuing works based on the source code:

How p-limit Queues Tasks

The concurrency limiter uses a two-phase queueing mechanism to manage task execution. Here's how it works:

1. The Queue Structure [index.js:L14, L57-58]

The library uses the yocto-queue package (listed in package.json dependencies) as its internal queue. When a task is submitted via limit(fn), it enters this queue.

2. Task Enqueueing Process [index.js:L44-61]

When you call limit(function_, ...args), the enqueue function handles it:

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

    // Queue the internal resolve function instead of the run function
    // to preserve the asynchronous execution context.
    new Promise(internalResolve => {
        queueItem.run = internalResolve;
        queue.enqueue(queueItem);
    }).then(run.bind(undefined, function_, resolve, arguments_));

    // Start processing immediately if we haven't reached the concurrency limit
    if (activeCount < concurrency) {
        resumeNext();
    }
};

Key points:

  • A queueItem object is created containing a reject function (for error handling)
  • The internal run function is assigned via a Promise callback to preserve async execution context
  • The item is added to the queue
  • If we haven't hit the concurrency limit, resumeNext() is called immediately

3. Task Execution [index.js:L29-42]

The resumeNext function processes queued tasks:

const resumeNext = () => {
    // Process the next queued function if we're under the concurrency limit
    if (activeCount < concurrency && queue.size > 0) {
        activeCount++;
        queue.dequeue().run();
    }
};

This checks if there's room to run more tasks (activeCount < concurrency) and dequeues the next item to execute it.

4. Task Completion [index.js:L23-26]

When a task finishes, the next function is called:

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

This decrements the active count and immediately tries to process the next pending task.

Example Flow

With pLimit(1) and 3 tasks:

  1. Task 1 is submitted → activeCount = 0 < 1, so it runs immediately (activeCount = 1)
  2. Task 2 is submitted → activeCount = 1, not < 1, so it waits in queue
  3. Task 3 is submitted → activeCount = 1, not < 1, so it waits in queue
  4. Task 1 finishes → activeCount = 0, resumeNext() dequeues Task 2 (activeCount = 1)
  5. Task 2 finishes → activeCount = 0, resumeNext() dequeues Task 3 (activeCount = 1)
  6. Task 3 finishes → activeCount = 0

You can inspect pending tasks with limit.pendingCount and running tasks with limit.activeCount [index.js:L66-73].

FOLLOW_UPS:

  • What happens when I call clearQueue()?
  • How does dynamic concurrency adjustment work?
  • How are async contexts preserved during queueing?

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.