Skip to main content

Яка різниця між promise.all i promise.allsettled?

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

Теорія

TL;DR

  • Promise.all як замовлення в ресторані на весь стіл: одна страва не вийшла, скасовується все. Promise.allSettled відстежує кожне замовлення окремо.
  • Promise.all зупиняється на першому відхиленні і ігнорує решту.
  • Promise.allSettled завжди завершується з [{status, value}, {status, reason}, ...].
  • Потрібні всі результати перед наступним кроком? all. Потрібно відстежити кожен наслідок? allSettled.
  • Обидва зберігають порядок вхідного масиву, а не порядок виконання.

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

javascript
const p1 = Promise.resolve('дані користувача'); const p2 = Promise.reject('помилка БД'); const p3 = new Promise(resolve => setTimeout(() => resolve('дані кешу'), 100)); // Зупиняється на p2, результат p3 ігнорується Promise.all([p1, p2, p3]).catch(err => console.log('all:', err)); // Виведе: all: помилка БД // Чекає всіх трьох, завжди завершується успішно Promise.allSettled([p1, p2, p3]).then(results => console.log(results)); // Виведе: // [ // { status: 'fulfilled', value: 'дані користувача' }, // { status: 'rejected', reason: 'помилка БД' }, // { status: 'fulfilled', value: 'дані кешу' } // ]

Коли p2 відхиляється, Promise.all перестає чекати на p3. Promise.allSettled збирає всі три результати і завершується нормально.

Головна різниця

Promise.all повертає масив значень, але тільки якщо всі проміси виконались. Перше відхилення одразу відхиляє зовнішній проміс з тією ж помилкою. Решта промісів продовжують виконуватись, але їхні результати ніхто не збирає. Promise.allSettled ніколи не відхиляється. Він завершується масивом об'єктів, де кожен має поле status ('fulfilled' або 'rejected') і або value, або reason.

Коли що використовувати

  • Всі результати потрібні перед наступним кроком (дані для кожного блоку дашборду): Promise.all
  • Часткові помилки прийнятні (масове завантаження зображень, відправка аналітичних подій): Promise.allSettled
  • Потрібно зафіксувати або перевірити кожен результат, включно з помилками: Promise.allSettled
  • Операції залежать одна від одної (потрібні дані користувача перед запитом прав доступу): Promise.all
  • Треба завжди відповідати клієнту, навіть якщо частина запитів зазнала помилки: Promise.allSettled

Порівняльна таблиця

ХарактеристикаPromise.allPromise.allSettled
Завершується колиВсі проміси виконаніВсі проміси завершені
Відхиляється колиБудь-який відхиленоНіколи
Значення результатуМасив значеньМасив {status, value/reason}
Порядок збереженоТакТак
Порожній масивЗавершується з []Завершується з []
Не-проміс у масивіСтає resolvedСтає resolved
Типове застосуванняКритичні пакетні операціїПеревірки стану, масові завантаження

Як це працює всередині

Обидва методи перебирають вхідний масив і ведуть лічильник незавершених промісів. Promise.all приєднує один обробник відхилення, який спрацьовує на першій помилці і зупиняє все. Promise.allSettled приєднує .then() і .catch() до кожного промісу окремо, додаючи об'єкт-статус до масиву результатів після кожного завершення. Коли лічильник доходить до нуля, зовнішній проміс завершується з повним масивом. Обидва є стандартом ES2020 і доступні з Node.js 12.9+.

Типові помилки

Очікувати від Promise.all часткових результатів

javascript
// Неправильно: .then() ніколи не спрацює, якщо rejectingP відхилено Promise.all([p1, rejectingP, p3]) .then(results => console.log(results)); // Правильно: використай allSettled і відфільтруй успішні Promise.allSettled([p1, rejectingP, p3]) .then(results => results .filter(r => r.status === 'fulfilled') .map(r => r.value) );

Забути, що allSettled обгортає результати в об'єкти

javascript
// Неправильно: result це об'єкт, а не значення for (const result of await Promise.allSettled(promises)) { processData(result); // передає {status, value} замість реальних даних } // Правильно: спершу перевір статус for (const result of await Promise.allSettled(promises)) { if (result.status === 'fulfilled') processData(result.value); else logError(result.reason); }

Використовувати allSettled коли операції залежать одна від одної

javascript
// Неправильно: продовжує з undefined, якщо fetchUser відхилено const results = await Promise.allSettled([fetchUser(id), fetchPermissions(id)]); const user = results[0].value; // undefined якщо відхилено renderDashboard(user); // падає // Правильно: для залежних даних використовуй Promise.all const [user, permissions] = await Promise.all([fetchUser(id), fetchPermissions(id)]);

Передавати не-проміси не розуміючи поведінки

javascript
// Працює, але приховує наміри Promise.all([1, 'hello', fetch('/api/data')]); // 1 і 'hello' стають Promise.resolve(1) і Promise.resolve('hello') // Краще явно описувати, що запускається паралельно

Де зустрічається в реальних проектах

  • React Query: useQueries використовує allSettled всередині, щоб кожен запит мав незалежний стан завантаження та помилки
  • Next.js: логіка fallback у getStaticPaths під час білду використовує allSettled
  • Express batch-ендпоінти: allSettled дозволяє завжди відповідати клієнту, навіть якщо частина запитів до БД зазнала помилки
  • Puppeteer: allSettled при скрейпінгу, коли деякі селектори можуть бути відсутні на сторінці
  • CLI-інструменти на Node.js: allSettled для масового читання файлів, де деякі можуть бути відсутні

Питання на співбесіді

Q: Що повернуть обидва методи, якщо передати порожній масив?
A: Обидва одразу завершаться з []. Без відхилення, без очікування.

Q: Що буде, якщо в масиві замість промісу звичайне число?
A: Обидва конвертують його в Promise.resolve(value). З allSettled це стане {status: 'fulfilled', value: число}.

Q: Чи відповідає порядок результатів порядку вхідного масиву?
A: Так, завжди. Порядок результатів відповідає вхідному масиву, а не порядку виконання промісів.

Q: Як відрізняється Promise.any() від цих двох?
A: Promise.any() завершується успішно, як тільки перший проміс виконається, і відхиляється лише якщо всі проміси відхилені. Promise.allSettled завжди чекає всіх і ніколи не відхиляється.

Q (senior): Як реалізувати поліфіл для Promise.allSettled у середовищах без підтримки?
A: Обгорни кожен проміс через .then(value => ({status: 'fulfilled', value})).catch(reason => ({status: 'rejected', reason})), а потім передай масив у Promise.all. Кожна обгортка завжди завершується успішно, тому Promise.all ніколи не відхиляється і повертає результати у тому ж форматі, що й allSettled.

Приклади

Запит до кількох API-ендпоінтів

javascript
const userIds = [1, 2, 3]; try { const users = await Promise.all( userIds.map(id => fetch(`/api/users/${id}`).then(r => r.json())) ); console.log(users); // [user1, user2, user3] } catch (err) { // Один запит зазнав помилки, нічого не відображається console.error('Запит не вдався:', err); }

Використовуй цей підхід, коли всі дані потрібні перед рендером. Якщо другий користувач недоступний, показувати решту може ввести в оману.

Express-ендпоінт з частковим успіхом

javascript
app.get('/batch/users', async (req, res) => { const ids = req.query.ids.split(','); const results = await Promise.allSettled( ids.map(id => fetchUserFromDB(id)) ); const found = results .filter(r => r.status === 'fulfilled') .map(r => r.value); const failed = results .filter(r => r.status === 'rejected') .map((r, i) => ({ id: ids[i], error: r.reason.message })); res.json({ found, failed }); // Завжди відповідає, навіть якщо частина запитів до БД зазнала помилки });

Клієнт завжди отримує відповідь і точно знає, які ID спрацювали, а які ні. Цей патерн часто зустрічається в адмін-панелях і ендпоінтах для пакетних операцій.

Масове читання файлів з відстеженням помилок

javascript
const fs = require('fs').promises; const filePaths = ['./data/a.json', './data/b.json', './data/missing.json']; const results = await Promise.allSettled( filePaths.map(path => fs.readFile(path, 'utf-8').then(JSON.parse)) ); results.forEach((result, i) => { if (result.status === 'fulfilled') { console.log(`Завантажено ${filePaths[i]}:`, result.value); } else { console.warn(`Пропущено ${filePaths[i]}:`, result.reason.message); } }); // Обробляє a.json і b.json, виводить попередження для missing.json

Я використовував цей патерн у скриптах міграції даних, де деякі вихідні файли були необов'язковими. З Promise.all один відсутній файл зупиняв увесь процес. Promise.allSettled обробляє все що можливо і повідомляє про те, що не вдалось.

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

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

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

Дочитали статтю?