Skip to main content

Promise.all() у JavaScript

Promise.all() це статичний метод класу Promise, який приймає масив (або будь-який ітерований об'єкт) промісів і повертає новий проміс. Цей проміс виконується успішно (fulfilled), коли всі проміси зі списку завершились успішно, і відхиляється (rejected) щойно перший із них завершився з помилкою.

Теорія

TL;DR

  • Promise.all(iterable) повертає новий проміс, який агрегує список промісів.
  • fulfilled: коли всі вхідні проміси стали fulfilled. Значення це масив результатів у тому самому порядку, у якому проміси передали, а не в порядку завершення.
  • rejected: одразу після першого відхилення, з причиною саме цього відхилення (поведінка fail-fast).
  • Решта промісів не скасовуються, вони виконуються до кінця, але їхні результати вже ніхто не побачить.
  • Звичайні значення (не проміси) автоматично загортаються через Promise.resolve().
  • Потрібні всі результати навіть із помилками? Тоді Promise.allSettled(), який не відхиляється ніколи.

Швидкий приклад

javascript
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"]

Синтаксис максимально простий:

javascript
Promise.all(iterable)

де iterable це, як правило, масив промісів.

Як воно працює

  1. Усі проміси всередині Promise.all([...]) виконуються паралельно: точніше, кожен із них уже стартував незалежно від інших, бо проміс починає роботу в момент створення.
  2. Promise.all() лише чекає на їхнє завершення, він нічого не запускає сам.
  3. Якщо всі завершились успішно, повертається новий проміс у стані fulfilled, а його значення це масив результатів у тому самому порядку, що й вхідні проміси.

Приклад із затримками:

javascript
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 секунди, тобто за часом найповільнішого проміса, а не за сумою всіх затримок:

javascript
Результати: [ 'Перший', 'Другий', 'Третій' ]

Порядок результатів і не проміси

Результати впорядковані за позицією у вхідному масиві, навіть якщо один проміс виконується суттєво довше за інші:

javascript
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():

javascript
Promise.all([42, Promise.resolve('OK')]) .then(console.log); // [42, "OK"]

Що станеться, якщо один із промісів відхилиться

Якщо хоча б один проміс усередині Promise.all перейде у стан rejected, увесь Promise.all() негайно відхиляється, а результатом буде перша спіймана помилка.

javascript
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));

Вивід:

javascript
Відхилено: Помилка!

Решта промісів (p1, p3) усе одно виконаються, але їхні результати ігноруються, бо Promise.all() уже перейшов у стан rejected, а проміс змінює стан лише один раз.

Як обробити помилки, не перериваючи виконання

Якщо потрібно, щоб усі проміси відпрацювали незалежно, а помилки не переривали загальний результат, використовуйте Promise.allSettled():

javascript
Promise.allSettled([ Promise.resolve('OK'), Promise.reject('Помилка'), Promise.resolve('Ще OK') ]).then(console.log);

Вивід:

javascript
[ { status: "fulfilled", value: "OK" }, { status: "rejected", reason: "Помилка" }, { status: "fulfilled", value: "Ще OK" } ]

Promise.allSettled() не відхиляється ніколи і повертає масив об'єктів з полями status та value або reason.

Альтернатива для Promise.all(), якщо ви все ж хочете лишитись на ньому: перехопіть помилку в кожному промісі окремо, тоді жодне відхилення не дійде до агрегатора.

javascript
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() пізніше, ви ризикуєте отримати необроблене відхилення ще до виклику.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.