Promise.all() у JavaScript
Promise.all() це статичний метод класу Promise, який приймає масив (або будь-який ітерований об'єкт) промісів і повертає новий проміс. Цей проміс виконується успішно (fulfilled), коли всі проміси зі списку завершились успішно, і відхиляється (rejected) щойно перший із них завершився з помилкою.
Теорія
TL;DR
Promise.all(iterable)повертає новий проміс, який агрегує список промісів.fulfilled: коли всі вхідні проміси сталиfulfilled. Значення це масив результатів у тому самому порядку, у якому проміси передали, а не в порядку завершення.rejected: одразу після першого відхилення, з причиною саме цього відхилення (поведінка fail-fast).- Решта промісів не скасовуються, вони виконуються до кінця, але їхні результати вже ніхто не побачить.
- Звичайні значення (не проміси) автоматично загортаються через
Promise.resolve(). - Потрібні всі результати навіть із помилками? Тоді
Promise.allSettled(), який не відхиляється ніколи.
Швидкий приклад
const p1 = Promise.resolve('A');
const p2 = Promise.resolve('B');
const p3 = Promise.resolve('C');
// Проміс від Promise.all чекає на всі три одразу.
Promise.all([p1, p2, p3]).then(values => console.log(values));
// ["A", "B", "C"]Синтаксис максимально простий:
Promise.all(iterable)де iterable це, як правило, масив промісів.
Як воно працює
- Усі проміси всередині
Promise.all([...])виконуються паралельно: точніше, кожен із них уже стартував незалежно від інших, бо проміс починає роботу в момент створення. Promise.all()лише чекає на їхнє завершення, він нічого не запускає сам.- Якщо всі завершились успішно, повертається новий проміс у стані
fulfilled, а його значення це масив результатів у тому самому порядку, що й вхідні проміси.
Приклад із затримками:
const p1 = new Promise(res => setTimeout(() => res('Перший'), 1000));
const p2 = new Promise(res => setTimeout(() => res('Другий'), 2000));
const p3 = new Promise(res => setTimeout(() => res('Третій'), 500));
Promise.all([p1, p2, p3])
.then(results => console.log('Результати:', results))
.catch(err => console.error('Помилка:', err));Вивід приблизно через 2 секунди, тобто за часом найповільнішого проміса, а не за сумою всіх затримок:
Результати: [ 'Перший', 'Другий', 'Третій' ]Порядок результатів і не проміси
Результати впорядковані за позицією у вхідному масиві, навіть якщо один проміс виконується суттєво довше за інші:
Promise.all([
Promise.resolve('A'),
new Promise(res => setTimeout(() => res('B'), 2000)),
Promise.resolve('C')
]).then(console.log); // ["A", "B", "C"]Якщо у масиві трапляються не проміси, Promise.all() автоматично «загортає» звичайні значення у Promise.resolve():
Promise.all([42, Promise.resolve('OK')])
.then(console.log); // [42, "OK"]Що станеться, якщо один із промісів відхилиться
Якщо хоча б один проміс усередині Promise.all перейде у стан rejected, увесь Promise.all() негайно відхиляється, а результатом буде перша спіймана помилка.
const p1 = Promise.resolve('OK');
const p2 = Promise.reject('Помилка!');
const p3 = Promise.resolve('Ще OK');
Promise.all([p1, p2, p3])
.then(results => console.log('Успіх:', results))
.catch(err => console.error('Відхилено:', err));Вивід:
Відхилено: Помилка!Решта промісів (p1, p3) усе одно виконаються, але їхні результати ігноруються, бо Promise.all() уже перейшов у стан rejected, а проміс змінює стан лише один раз.
Як обробити помилки, не перериваючи виконання
Якщо потрібно, щоб усі проміси відпрацювали незалежно, а помилки не переривали загальний результат, використовуйте Promise.allSettled():
Promise.allSettled([
Promise.resolve('OK'),
Promise.reject('Помилка'),
Promise.resolve('Ще OK')
]).then(console.log);Вивід:
[
{ status: "fulfilled", value: "OK" },
{ status: "rejected", reason: "Помилка" },
{ status: "fulfilled", value: "Ще OK" }
]Promise.allSettled() не відхиляється ніколи і повертає масив об'єктів з полями status та value або reason.
Альтернатива для Promise.all(), якщо ви все ж хочете лишитись на ньому: перехопіть помилку в кожному промісі окремо, тоді жодне відхилення не дійде до агрегатора.
const safe = p => p.catch(err => ({ error: err }));
Promise.all([p1, p2, p3].map(safe)).then(console.log);Порівняння: all, allSettled, race
| Метод | Що робить | Успіх | Помилка |
|---|---|---|---|
Promise.all() | Чекає, поки виконаються всі проміси | Повертає масив результатів | Відхиляється на першій же помилці |
Promise.allSettled() | Чекає на всіх, навіть із помилками | Повертає масив об'єктів {status, value/reason} | Не відхиляється ніколи |
Promise.race() | Повертає результат першого завершеного проміса | Може бути і успіх, і помилка | Відхиляється, якщо першим завершився відхилений проміс |
Проста аналогія:
Уявіть, що ви чекаєте на три доставки:
Promise.all(): ви чекаєте на всі посилки разом. Якщо хоча б одна загубилась, ви скасовуєте все замовлення.Promise.allSettled(): ви так само чекаєте на всі, і навіть якщо одна пошкоджена, ви все одно отримуєте звіт про кожну.
Типові помилки
- Думати, що
Promise.all()запускає проміси. Він лише чекає: проміси стартували ще в момент створення, тому обгортка нічого не паралелізує додатково. - Забути
.catch()абоtry/catch. Відхилення всерединіPromise.all()без обробника даєunhandled promise rejection, а в Node.js це може завалити процес. - Очікувати, що інші проміси скасуються. Проміси не скасовні: після першого відхилення решта далі працює, споживає ресурси і може навіть кинути власну необроблену помилку.
- Чекати результати в порядку завершення. Порядок у масиві завжди відповідає порядку входу, тому не сортуйте результати «за швидкістю».
- Використовувати
Promise.all()там, де потрібні часткові результати. Якщо один із десяти запитів має право впасти, берітьPromise.allSettled(). - Передавати вже відхилений проміс у масив зарано. Наприклад, створивши
Promise.reject()заздалегідь і передавши його вPromise.all()пізніше, ви ризикуєте отримати необроблене відхилення ще до виклику.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.