Интервью-вопрос
Что будет, если в Promise.all один Promise выдаст ошибку
Promise.all отклонится с ошибкой первого отклоненного Promise. Остальные операции не отменяются автоматически. Во frontend-коде важно заранее выбрать стратегию обработки ошибок.
- Добавлен
- Редакция
Подготовьте короткий ответ и пару деталей на случай уточняющих вопросов.
Мини-квиз
Проверка перед разбором
Несколько быстрых вопросов перед разбором. Так проще поймать места, которые только кажутся понятными.
Вопрос 1 из 60 правильно
Разбор
Разобраться, а не зазубрить
Дальше разбираем суть, типичные уточнения и места, где легко сказать лишнее или перепутать термины.
Базовая идея
Promise.all принимает набор значений или Promise и возвращает новый Promise. Он успешно завершается только тогда, когда успешно завершились все входные Promise. Если хотя бы один входной Promise стал rejected, общий Promise тоже становится rejected.
Главное слово для интервью: fail fast. Promise.all быстро сообщает об ошибке и не ждет, пока вы получите полный отчет по каждому элементу. Но это не значит, что он физически остановил остальные задачи.
В успешном сценарии массив значений идет в порядке входного массива. Скорость завершения отдельных Promise на порядок значений не влияет.
Пример с первой ошибкой
В этом примере ошибка приходит раньше, чем завершается медленный Promise. Общий catch сработает по первой ошибке.
const slowUser = new Promise((resolve) => {
setTimeout(() => resolve("user"), 1000);
});
const fastError = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error("profile failed")), 100);
});
const settings = Promise.resolve("settings");
Promise.all([slowUser, fastError, settings])
.then((values) => {
console.log(values);
})
.catch((error) => {
console.error(error.message); // "profile failed"
});Практический вывод простой. Код в then не выполнится, потому что общий результат уже отклонен. При этом slowUser не исчезает и может завершиться позже.
Что важно во frontend-коде
Частая ошибка в интерфейсах: считать, что после первой ошибки остальные запросы уже не важны и точно остановлены. На самом деле запросы могут продолжать идти. Сервер может выполнить действие, а поздний ответ может попытаться обновить состояние.
Плохой пример: вы отправили несколько независимых изменений через Promise.all, одно упало, и вы показываете пользователю "ничего не сохранилось". Но часть запросов могла успешно изменить данные на сервере. Так появляется рассинхронизация UI и backend.
Безопаснее заранее решить, какой контракт нужен. Если операция атомарная с точки зрения пользователя, лучше иметь серверный endpoint, который применяет изменения транзакционно. Если операции независимые, показывайте результат по каждой операции через Promise.allSettled или свой формат результата.
Как выбрать подход
Используйте Promise.all и обрабатывайте общий catch.Используйте Promise.allSettled.Обработайте catch у конкретного Promise и верните явный fallback.Добавьте явную отмену, например AbortController для fetch.Как обработать некритичную ошибку
Иногда падение одного Promise не должно ломать всю страницу. Например, основной профиль нужен обязательно, а блок рекомендаций можно заменить пустым списком. В таком случае обработайте ошибку на уровне конкретного Promise.
const profilePromise = fetchProfile();
const recommendationsPromise = fetchRecommendations().catch((error) => {
console.warn("Recommendations are unavailable", error);
return [];
});
const [profile, recommendations] = await Promise.all([
profilePromise,
recommendationsPromise,
]);Так делать можно, если fallback действительно безопасен. Не превращайте все ошибки в null без разбора. Иначе UI будет выглядеть успешным, хотя важная часть данных потеряна.
Когда нужен Promise.allSettled
Promise.allSettled подходит, когда вам нужен полный отчет: что выполнилось, что упало и с какой причиной. Он не срывается на первой ошибке.
const results = await Promise.allSettled([
fetchHeader(),
fetchSidebar(),
fetchFeed(),
]);
for (const result of results) {
if (result.status === "fulfilled") {
renderBlock(result.value);
} else {
renderBlockError(result.reason);
}
}Это хороший выбор для независимых блоков страницы, массовой загрузки файлов, пакетных операций и админских экранов. Пользователь получает честную картину, а вы не теряете успешные результаты из-за одного сбоя.
Отмена и side effects
Promise.all не отменяет операции. Если вам надо остановить сетевые запросы после ошибки или при уходе со страницы, используйте механизм конкретного API. Для fetch это обычно AbortController.
const controller = new AbortController();
try {
await Promise.all([
fetch("/api/user", { signal: controller.signal }),
fetch("/api/settings", { signal: controller.signal }),
]);
} catch (error) {
controller.abort();
showError(error);
}Такой код показывает идею, но отмена в catch не откатывает уже выполненные действия на сервере. Она только просит отменить операции, которые еще можно остановить. В реальном компоненте еще нужен cleanup при размонтировании. Иначе поздний ответ может прийти, когда экран уже закрыт. Пользователь увидит устаревшее состояние или лишнюю ошибку.
useEffect(() => {
const controller = new AbortController();
let active = true;
async function loadJson(url) {
const response = await fetch(url, { signal: controller.signal });
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
return response.json();
}
async function load() {
try {
const [user, settings] = await Promise.all([
loadJson("/api/user"),
loadJson("/api/settings"),
]);
if (!active) return;
setData({ user, settings });
} catch (error) {
if (!active || error.name === "AbortError") return;
setError(error);
}
}
load();
return () => {
active = false;
controller.abort();
};
}, []);Безопасная идея здесь не в самом Promise.all. Она в контракте вокруг него: при уходе со страницы запросы отменяются, а поздний результат не обновляет состояние.
- 1Запускаете независимые операции параллельно
- 2Ловите общую ошибку у Promise.all
- 3Показываете понятное состояние ошибки
- 4При необходимости явно отменяете лишние запросы
- 1Считаете, что Promise.all отменит остальные операции
- 2Не учитываете side effects от продолжающихся запросов
- 3Скрываете причину ошибки общим сообщением
- 4Теряете частичные результаты без allSettled
Практический вывод
Хороший короткий ответ на интервью может звучать так:
Если один Promise в Promise.all отклонится, весь Promise.all тоже отклонится с этой ошибкой. Это fail fast поведение. Остальные Promise не отменяются автоматически, они могут продолжить выполнение. Если мне нужны результаты всех операций, я выберу Promise.allSettled. Если ошибка отдельной операции ожидаемая и некритичная, обработаю ее внутри этого Promise и верну явный fallback.
Такой ответ показывает не только знание метода. Он показывает понимание риска для интерфейса: частичные side effects, потерянные результаты и неверное состояние загрузки.
Частые ошибки
Где обычно ошибаются
Проверьте формулировки, которые звучат уверенно, но на интервью быстро выдают пробелы.
- 1
Думать, что остальные Promise отменяются
Promise.allотклоняет только свой результирующий Promise. Запросы, таймеры и записи, которые уже стартовали, продолжают жить. Если это важно для UI, добавьте явную отмену или защиту от позднего обновления состояния. - 2
Путать первую ошибку по времени с первым элементом массива
Ошибка приходит от того Promise, который первым перешел в состояниеrejected. Его позиция во входном массиве не обязана быть первой. На интервью скажите именно "первый отклоненный по времени". - 3
Ждать частичные результаты от Promise.all
При отклонении вы не получите массив успешных значений черезthen. Если интерфейсу нужны данные по каждому виджету, используйтеPromise.allSettledили возвращайте явные объекты результата для каждого запроса. - 4
Скрывать критичные ошибки локальным catch
promise.catch(() => null)делает ошибку похожей на нормальное значение. Так легко получить тихо сломанный UI. Безопаснее вернуть объект вида{ ok: false, error }или обработать только ожидаемую ошибку. - 5
Использовать Promise.all для зависимых шагов
Если второй запрос требует id из первого, параллельный запуск даст гонки или неверные параметры. Для зависимых операций используйте последовательныйawait.Promise.allоставляйте для независимых задач. - 6
Не защищать состояние компонента от позднего ответа
После первой ошибки общийcatchуже мог показать ошибку, но остальные запросы еще завершаются. В React это может обновить устаревший экран. ИспользуйтеAbortController, cleanup вuseEffectили проверку актуального запроса передsetState.
Follow-up
Что могут спросить дальше
Короткие ответы на вопросы, которые помогают проверить понимание Promise.all, ошибок и параллельных операций.
Живые ответы
Видео с похожим вопросом
Если найдем публичные интервью с таким вопросом, добавим их сюда. Их удобно смотреть после теории, чтобы свериться с живыми ответами.
Пока видео нет. Когда появятся подходящие публичные интервью, добавим их в этот блок, чтобы можно было сравнить разбор с тем, как отвечают реальные кандидаты.
Что такое await 😎
await приостанавливает выполнение async-функции до завершения Promise и возвращает его результат или выбрасывает ошибку. Разбираем, что именно он блокирует, как обрабатывать ошибки и когда запускать операции параллельно.
Чем пользоваться для выполнения запросов нескольких Promise 😎
Для нескольких асинхронных операций выбирают Promise.all, Promise.allSettled, Promise.race, Promise.any или последовательное выполнение по задаче. Разбираем, как выбрать метод и не сломать обработку ошибок во фронтенде.