All Snippets Snippet JavaScript

Promise Concurrency Queue

Queue Running (max: 3) Done P5

Run async operations with a concurrency limit to prevent overwhelming servers and network resources.

D
Kunwar "AKA" AJ Sharing what I have learned
Feb 21, 2026 2 min JavaScript

The Pattern

promiseQueue.js
class PromiseQueue {
    constructor(concurrency = 3) {
        this.concurrency = concurrency;
        this.running = 0;
        this.queue = [];
    }

    add(asyncFn) {
        return new Promise((resolve, reject) => {
            this.queue.push({ asyncFn, resolve, reject });
            this.flush();
        });
    }

    flush() {
        while (this.running < this.concurrency && this.queue.length > 0) {
            const { asyncFn, resolve, reject } = this.queue.shift();
            this.running++;

            asyncFn()
                .then(resolve)
                .catch(reject)
                .finally(() => {
                    this.running--;
                    this.flush();
                });
        }
    }
}

What Happens Under the Hood

Let us trace what happens when you queue 5 API calls with a concurrency limit of 2:

execution-flow.txt
add(task1) → running: 0 < 2 → START task1 → running: 1
add(task2) → running: 1 < 2 → START task2 → running: 2
add(task3) → running: 2 = 2 → QUEUED (position 1)
add(task4) → running: 2 = 2 → QUEUED (position 2)
add(task5) → running: 2 = 2 → QUEUED (position 3)

task1 completes → running: 1 → flush() → START task3 → running: 2
task2 completes → running: 1 → flush() → START task4 → running: 2
task3 completes → running: 1 → flush() → START task5 → running: 2
task4 completes → running: 1 → flush() → queue empty, done
task5 completes → running: 0 → flush() → queue empty, done

At no point did more than 2 tasks run simultaneously

The finally block is the engine. Every time a task completes — whether it succeeds or fails — it decrements the counter and calls flush() again. This creates a self-sustaining loop: finished tasks automatically pull new ones from the queue without any external orchestration.

Why This Matters

impact.txt
Promise.all with 1000 URLs:
  → 1000 simultaneous HTTP connections
  → Server returns 429 (Too Many Requests)
  → Network adapter overwhelmed
  → Browser tab freezes

PromiseQueue with concurrency 5:
  → Max 5 connections at a time
  → Server handles the load
  → Results stream in steadily
  → UI stays responsive

Usage Example

usage.js
// Upload images with max 3 concurrent uploads
const queue = new PromiseQueue(3);

const uploadResults = await Promise.all(
    files.map(file =>
        queue.add(() => uploadToServer(file))
    )
);

// Fetch paginated API data without overwhelming the server
const queue = new PromiseQueue(5);
const pages = Array.from({ length: 50 }, (_, i) => i + 1);

const allData = await Promise.all(
    pages.map(page =>
        queue.add(() =>
            fetch(`/api/records?page=${page}`)
                .then(res => res.json())
        )
    )
);

Use this whenever you have many async operations that would overwhelm a resource if fired all at once. File uploads, API pagination, database batch operations, and web scraping are common cases. Set concurrency based on what the target can handle: 3 to 5 for external APIs, 10 to 20 for internal services, and 1 for operations that must run sequentially.