How to run async tasks in parallel with a limit
Short answer
Start N workers. Each worker takes the next task from a shared counter until the tasks run out:
async function mapWithLimit(items, limit, fn) {
const results = new Array(items.length);
let next = 0;
const worker = async () => {
while (next < items.length) {
const index = next++;
results[index] = await fn(items[index]);
}
};
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
return results;
}
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
let running = 0;
let maxRunning = 0;
const results = await mapWithLimit([1, 2, 3, 4, 5, 6, 7], 3, async (n) => {
running++;
maxRunning = Math.max(maxRunning, running);
await delay(50);
running--;
return n * 10;
});
console.log(results.join(', '));
console.log('at most at once:', maxRunning);Output:
10, 20, 30, 40, 50, 60, 70
at most at once: 3With fetch
That is how you download a hundred pages without overloading the server:
const ids = Array.from({ length: 100 }, (_, i) => i + 1);
const products = await mapWithLimit(ids, 5, (id) => fetch(`/api/products/${id}`).then((r) => r.json()));In batches
A simpler way is batches: run N tasks, wait for all of them, then the next N. It is a little slower, because each batch waits for its slowest task:
const delay = (ms, value) => new Promise((resolve) => setTimeout(() => resolve(value), ms));
const items = [1, 2, 3, 4, 5];
const results = [];
for (let i = 0; i < items.length; i += 2) {
const batch = items.slice(i, i + 2);
results.push(...(await Promise.all(batch.map((n) => delay(20, n * n)))));
}
console.log(results.join(', '));Output:
1, 4, 9, 16, 25Common mistake
items.map(fn) starts all the tasks at once. Promise.all() only waits for them; it does not limit anything:
let running = 0;
let maxRunning = 0;
const task = async () => {
running++;
maxRunning = Math.max(maxRunning, running);
await new Promise((resolve) => setTimeout(resolve, 10));
running--;
};
await Promise.all(Array.from({ length: 50 }, task));
console.log(maxRunning);Output:
50Functions used in this recipe
- Promise.all — Waits for all promises in parallel.
- async function — A function that always returns a promise and lets you use await inside.