> Как отлавливать ошибки промисов в JavaScript (JavaScript)
Уровень: senior · Роль: frontend · Категория: Технические вопросы
Компании: циан, Яндекс
Стек: JavaScript
> Пример ответа
Короткий ответ
Основной способ - метод .catch() на промисах и блок try/catch в async/await. Для глобального перехвата необработанных rejections используются события unhandledrejection в браузере и process.on('unhandledRejection') в Node.js. Важно помнить: ошибка в цепочке промисов "падает" до ближайшего .catch(), а в async-функции - до ближайшего try/catch. Если rejection не обработан - это утечка и потенциальный крах приложения.
Подробное объяснение
Промисы имеют три состояния: pending, fulfilled, rejected. Ошибка возникает при вызове reject() или при throw внутри executor/async-функции. Существует два синтаксических подхода:
-
Цепочки
.then().catch()-.catch()перехватывает ошибки из предыдущих.then()и самого промиса. Важно:.catch()возвращает новый промис, поэтому цепочку можно продолжать. -
async/await - синтаксический сахар над промисами.
awaitприостанавливает выполнение функции, а при rejection выбрасывает исключение, которое ловитсяtry/catch. Если исключение не поймано внутри async-функции - она возвращает rejected промис.
Глобальный перехват нужен для логирования и предотвращения "тихих" ошибок. В браузере: window.addEventListener('unhandledrejection', handler). В Node.js: process.on('unhandledRejection', handler). Событие rejectionhandled срабатывает, когда ошибка была обработана позже (после события unhandledrejection).
Также есть Promise.allSettled() - он не бросает ошибку, а возвращает массив результатов с полями status, value/reason. Это полезно, когда нужно обработать все промисы независимо от их исхода.
На практике
Для senior-уровня важно не просто знать синтаксис, а понимать:
- Порядок обработки:
.catch()ловит ошибки только из предыдущих звеньев цепочки, не из последующих. - Возврат из
.catch(): если вернуть значение - цепочка продолжится как успешная; если бросить снова - ошибка уйдёт дальше. - Асинхронные ошибки в
try/catch:try/catchне ловит ошибки из колбэков, например, изsetTimeoutилиaddEventListener. - Ошибки в конструкторе промиса: если в executor брошено исключение - промис автоматически становится rejected.
- Необработанные rejection в тестах: в Jest/Playwright они могут приводить к падению тестов, поэтому нужно явно обрабатывать или использовать
unhandledRejectionдля диагностики.
На практике часто используют утилиту to() (из golang-паттерна), которая возвращает [data, error] вместо try/catch - это упрощает чтение кода.
Пример кода
JAVASCRIPT// Базовый catchfetch('/api/user').then(res => res.json()).catch(err => console.error('Ошибка запроса:', err));// async/awaitasync function loadUser() {try {const res = await fetch('/api/user');const data = await res.json();return data;} catch (err) {console.error('Ошибка:', err);throw err; // пробрасываем дальше, если нужно}}// Глобальный перехват в браузереwindow.addEventListener('unhandledrejection', (event) => {console.warn('Необработанный rejection:', event.reason);event.preventDefault(); // предотвращает лог в консоль});// Promise.allSettledconst results = await Promise.allSettled([fetch('/api/a'),fetch('/api/b'),]);results.forEach(r => {if (r.status === 'fulfilled') {console.log('Успех:', r.value);} else {console.log('Ошибка:', r.reason);}});// Утилита to()async function to(promise) {try {const data = await promise;return [null, data];} catch (err) {return [err, null];}}const [err, user] = await to(fetch('/api/user'));if (err) {// обработка ошибки}
Как отвечать на собеседовании
Начни с базового .catch() и try/catch, затем переходи к глобальным обработчикам. Упомяни разницу между браузером и Node.js. Покажи понимание цепочек: что .catch() возвращает новый промис, что ошибка "проваливается" до ближайшего обработчика. Добавь про allSettled как альтернативу, когда не нужно прерывать выполнение. Если спросят про "тихие" ошибки - расскажи про unhandledrejection и почему это плохо для продакшена. Хорошо упомянуть паттерн to() как практический приём.
Что проверяет интервьюер
- Понимание асинхронной модели JavaScript: event loop, microtasks, порядок выполнения.
- Умение работать с цепочками промисов и их особенностями (возврат значений, проброс ошибок).
- Знание глобальных обработчиков и их назначения.
- Способность выбирать правильный инструмент под задачу:
.catch(),try/catch,allSettled,finally. - Понимание последствий необработанных rejection: утечки памяти, падение процесса в Node.js.
Типичные ошибки
- Забывают, что
try/catchне ловит ошибки внутриsetTimeoutили колбэков событий. - Путают порядок
.then()и.catch():.catch()после.then()ловит ошибки только из этого.then(), не из предыдущих. - Не возвращают промис из
.catch()и теряют цепочку. - Игнорируют
unhandledRejectionв Node.js - это приводит к падению процесса (в новых версиях Node). - Используют
Promise.all()без обработки ошибок, когда нужно получить частичные результаты - лучшеallSettled. - Пробрасывают ошибку из
catchбезthrow- и потом удивляются, что внешний код не видит ошибку.
> Похожие задачи по frontend
Какая конструкция может использоваться вместе с try-catch в JavaScript
Какой метод вызывается при наследовании, если метод определён в прототипе родителя и в экземпляре дочернего класса в JavaScript
Изменяется ли исходный массив при вызове метода sort в JavaScript и почему
Что такое самовызывающаяся функция (IIFE) в JavaScript
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью