Яка різниця між 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. - Обидва зберігають порядок вхідного масиву, а не порядок виконання.
Швидкий приклад
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.all | Promise.allSettled |
|---|---|---|
| Завершується коли | Всі проміси виконані | Всі проміси завершені |
| Відхиляється коли | Будь-який відхилено | Ніколи |
| Значення результату | Масив значень | Масив {status, value/reason} |
| Порядок збережено | Так | Так |
| Порожній масив | Завершується з [] | Завершується з [] |
| Не-проміс у масиві | Стає resolved | Стає resolved |
| Типове застосування | Критичні пакетні операції | Перевірки стану, масові завантаження |
Як це працює всередині
Обидва методи перебирають вхідний масив і ведуть лічильник незавершених промісів. Promise.all приєднує один обробник відхилення, який спрацьовує на першій помилці і зупиняє все. Promise.allSettled приєднує .then() і .catch() до кожного промісу окремо, додаючи об'єкт-статус до масиву результатів після кожного завершення. Коли лічильник доходить до нуля, зовнішній проміс завершується з повним масивом. Обидва є стандартом ES2020 і доступні з Node.js 12.9+.
Типові помилки
Очікувати від Promise.all часткових результатів
// Неправильно: .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 обгортає результати в об'єкти
// Неправильно: 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 коли операції залежать одна від одної
// Неправильно: продовжує з 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)]);Передавати не-проміси не розуміючи поведінки
// Працює, але приховує наміри
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-ендпоінтів
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-ендпоінт з частковим успіхом
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 спрацювали, а які ні. Цей патерн часто зустрічається в адмін-панелях і ендпоінтах для пакетних операцій.
Масове читання файлів з відстеженням помилок
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 обробляє все що можливо і повідомляє про те, що не вдалось.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.