Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Яка різниця між promise.all i promise.allsettled?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Promise.all** відхиляється одразу, щойно хоч один проміс відхилено. **Promise.allSettled** завжди завершується успішно, повертаючи `{status, value/reason}` для кожного промісу. ```js Promise.all([p1, rejectingP]).catch(err => ...); // відхиляється при першій помилці Promise.allSettled([p1, rejectingP]).then(results => ...); // завжди завершується ``` **Ключове:** `all` коли потрібні всі результати, `allSettled` коли часткові результати прийнятні.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**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.all | Promise.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` обробляє все що можливо і повідомляє про те, що не вдалось.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.