Как выполнять асинхронные задачи параллельно, но с ограничением
Короткий ответ
Запустите N обработчиков. Каждый берёт следующую задачу по общему счётчику, пока задачи не кончатся:
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);Результат:
10, 20, 30, 40, 50, 60, 70
at most at once: 3С fetch
Так можно скачать сотню страниц, не перегружая сервер:
const ids = Array.from({ length: 100 }, (_, i) => i + 1);
const products = await mapWithLimit(ids, 5, (id) => fetch(`/api/products/${id}`).then((r) => r.json()));Пачками
Способ проще — пачками: запустить N задач, дождаться всех, затем следующие N. Это чуть медленнее, потому что каждая пачка ждёт свою самую медленную задачу:
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(', '));Результат:
1, 4, 9, 16, 25Частая ошибка
items.map(fn) запускает все задачи сразу. Promise.all() только ждёт их и ничего не ограничивает:
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);Результат:
50Функции из этого ответа
- Promise.all — Ждёт выполнения всех промисов параллельно.
- async function — Функция, которая всегда возвращает промис и внутри которой можно использовать await.