forEach викликає колбек і ігнорує те, що він повертає. async-колбек повертає Promise, який ніхто не чекає: цикл завершується одразу, помилки губляться.
items.forEach(async (item) => { await save(item); });
console.log('готово'); // виведеться ДО того, як щось збережеться
Правильні варіанти залежно від задачі:
// Послідовно, по одному
for (const item of items) {
await save(item);
}
// Усі паралельно
await Promise.all(items.map((item) => save(item)));
Проблема «всіх паралельно»: тисяча елементів - тисяча одночасних запитів. API почне відповідати 429, браузер обмежить кількість з'єднань, база отримає піку навантаження.
Обмежена паралельність - не більше N одночасно:
async function mapLimit(items, limit, fn) {
const results = new Array(items.length);
let next = 0;
async function worker() {
while (next < items.length) {
const index = next++;
results[index] = await fn(items[index], index);
}
}
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
return results;
}
const saved = await mapLimit(items, 5, save);
Тут next++ безпечний, бо JavaScript однопотоковий: між читанням і збільшенням лічильника інший «воркер» виконатися не може.
На практиці часто беруть готові бібліотеки (p-limit, p-map), які додають ще й обробку помилок і скасування.