
Promise.all и allSettled: как обработать частичный сбой загрузки данных


Когда страница или модуль загружает несколько независимых данных сразу, ошибка в одном запросе не всегда должна ломать весь результат. Типичный случай: профиль пользователя пришел, статистика пришла, а блок рекомендаций упал. В этот момент главный вопрос не в том, как «поймать ошибку вообще», а как решить, нужен ли вам только полный набор данных или допустим частичный результат.
Для такой задачи важно различать два режима работы. Если без любого из ответов смысл результата теряется, подходит Promise.all: он завершится ошибкой, как только один из промисов будет отклонен. Если же части интерфейса независимы и полезно показать то, что уже удалось получить, удобнее Promise.allSettled: он дождется завершения всех промисов и вернет отдельный итог по каждому.
Полный результат или частичный: от этого зависит выбор
Promise.all хорош там, где данные образуют один цельный результат. Например, вы собираете карточку заказа, и без списка товаров, суммы и статуса оплаты ее нельзя корректно показать. Тогда ранний отказ удобен: код сразу переходит в ветку ошибки, а вы не тратите время на обработку набора, который все равно нельзя использовать целиком.
Promise.allSettled нужен в другой ситуации: данные приходят в независимые зоны интерфейса или в отчет, где полезен даже неполный ответ. Его результат — массив объектов со статусом каждого промиса. Это помогает не гадать, какой запрос упал, а явно увидеть: один fulfilled, другой rejected, третий fulfilled. При этом порядок результатов соответствует порядку входных промисов, даже если фактически они завершились в другом порядке.
Практический вывод простой: сначала решите, можно ли использовать частичный ответ без обмана пользователя. Если нет — берите Promise.all. Если да — сначала собирайте через Promise.allSettled, а потом явно решайте, какие блоки показать, что скрыть и где вывести сообщение об ошибке.
Три промиса с одним сбоем: что реально изменится
Ниже — одна и та же учебная ситуация в современной консоли браузера. У нас есть три независимых асинхронных запроса: профиль, статистика и рекомендации. Рекомендации завершаются ошибкой. Сначала посмотрим, как ведет себя Promise.all, потом — Promise.allSettled.
Учебный пример, условный. Исходные данные: профиль приходит через 120 мс, статистика — через 80 мс, рекомендации падают через 160 мс. Среда выполнения — консоль современного браузера.
function mockRequest(name, ms, data, shouldReject = false) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (shouldReject) {
reject(new Error(name + ': request failed'));
} else {
resolve({ name, data });
}
}, ms);
});
}
function createWidgetPromises() {
return [
mockRequest('profile', 120, { id: 7, name: 'Анна' }),
mockRequest('stats', 80, { visits: 42 }),
mockRequest('recommendations', 160, null, true)
];
}
(async () => {
try {
const result = await Promise.all(createWidgetPromises());
console.log('all result', result);
} catch (error) {
console.log('all error', error.message);
}
const settled = await Promise.allSettled(createWidgetPromises());
console.log('allSettled result', settled);
})();
Проверяемый результат здесь такой: в первой части вы получите сообщение all error recommendations: request failed, а массива с успешными значениями не будет. Во второй части вернется массив из трех элементов: два со статусом fulfilled и один со статусом rejected. Это и есть граница между «или все, или ошибка» и «покажи все, что смог загрузить».
Важно не приписывать Promise.all того, чего он не делает. Он не отменяет автоматически уже запущенные операции. Если один промис упал, остальные могут продолжать выполняться, но общий результат уже считается неуспешным. Поэтому разница между методами — не в запуске запросов, а в том, какой итог вы получите для дальнейшей логики.
Как сохранить успешные результаты и отдельно обработать сбой
После Promise.allSettled следующий шаг — привести ответы к форме, удобной для интерфейса. Частая ошибка — сразу разбирать массив вручную по индексам и дублировать проверки. Проще один раз нормализовать результаты: у каждого блока будет имя, флаг успеха, данные или текст ошибки.
(async () => {
const widgetNames = ['profile', 'stats', 'recommendations'];
const settled = await Promise.allSettled(createWidgetPromises());
const widgets = settled.map((item, index) => {
if (item.status === 'fulfilled') {
return {
widget: widgetNames[index],
ok: true,
data: item.value.data,
error: null
};
}
return {
widget: widgetNames[index],
ok: false,
data: null,
error: item.reason.message
};
});
const successfulCount = widgets.filter(item => item.ok).length;
console.log(widgets);
console.log('successful widgets:', successfulCount);
})();
Проверяемый результат: вы получите массив из трех объектов, где для profile и stats поле ok будет true, а для recommendations — false. Переменная successfulCount в этом наборе равна 2. Это уже удобно использовать дальше: например, отрисовать два готовых блока и один плейсхолдер с сообщением «не удалось загрузить рекомендации».
Если очень хочется остаться на Promise.all, частичный результат можно сохранить через локальную обработку каждого промиса: превращать ошибку в обычное значение через .catch(). Но в независимых запросах это часто менее прозрачно: успешные и ошибочные состояния смешиваются в одном массиве без явного поля статуса. Для чтения и поддержки кода allSettled обычно понятнее.
HTTP-статус и ошибка промиса — это не одно и то же
Отдельная ловушка связана с fetch. Даже если сервер ответил 404 или 500, сам промис fetch не обязан отклониться автоматически: вы все равно можете получить успешное завершение промиса с объектом ответа, у которого ok === false и соответствующий status. Поэтому для корректной работы с Promise.allSettled или Promise.all нужно явно превращать плохой HTTP-статус в ошибку.
Ниже учебная функция, которая имитирует это поведение и затем делает явную проверку. Такой подход полезен, потому что он приводит сетевые результаты к одному правилу: данные с нормальным статусом идут как успешное значение, а ответы с ошибочным статусом становятся отклоненным промисом.
function fakeFetch(url) {
const responses = {
'/api/profile': { status: 200, body: { id: 7, name: 'Анна' } },
'/api/stats': { status: 200, body: { visits: 42 } },
'/api/recommendations': { status: 404, body: { message: 'Not found' } }
};
const response = responses[url];
return Promise.resolve({
status: response.status,
ok: response.status >= 200 && response.status < 300,
async json() {
return response.body;
}
});
}
async function fetchJsonChecked(url) {
const response = await fakeFetch(url);
if (!response.ok) {
throw new Error('HTTP ' + response.status + ' for ' + url);
}
return response.json();
}
(async () => {
const settled = await Promise.allSettled([
fetchJsonChecked('/api/profile'),
fetchJsonChecked('/api/stats'),
fetchJsonChecked('/api/recommendations')
]);
console.log(settled);
})();
Проверка здесь тоже ясная: первые два элемента массива будут успешными, третий — отклоненным с причиной HTTP 404 for /api/recommendations. То есть ошибка сервера стала не просто числом в ответе, а полноценной веткой обработки. Это особенно важно, если вы хотите единообразно показывать состояние блоков загрузки.
Как выбрать подход в своей задаче и не потерять контроль
Чтобы не ошибиться с выбором, задайте себе три вопроса. Первый: можно ли безопасно показать пользователю неполный результат? Второй: независимы ли блоки данных друг от друга? Третий: есть ли у вас явная проверка HTTP-статуса, если запросы идут через fetch? Если на первый вопрос ответ «нет», то чаще нужен Promise.all. Если «да», а блоки независимы, почти всегда удобнее Promise.allSettled.
Полезно также проверять результат не только глазами, но и простой арифметикой. В нашем учебном кейсе было три запроса: два успешных и один с ошибкой. Значит, после нормализации должно получиться ровно три объекта, из них 2 с ok: true и 1 с ok: false. Такая проверка быстро ловит ошибки в маппинге, неверный порядок имен и пропущенные ветки обработки.
Итоговое практическое правило такое: используйте Promise.all для единого обязательного результата, а Promise.allSettled — для независимых частей интерфейса, где ценен даже частичный успех. И всегда отделяйте две проверки: «промис отклонен» и «HTTP-ответ получен, но статус плохой». Именно на этом месте чаще всего и теряются частичные результаты.
Если хотите закрепить тему на более широких задачах, можно посмотреть сохраненное описание каталога курса по продвинутому JavaScript: в программе подтверждена тема «Асинхронность и сетевые запросы: Promises, Async/await и Fetch». Если рассматриваете обучение, полезно заранее проверить для себя, есть ли в заданиях самостоятельная практика по частичным сбоям и есть ли понятная проверка результата, а не только разбор синтаксиса.





















