> Как перехватывать ошибки в промисах и чем это отличается от try-catch (JavaScript)

Уровень: senior · Роль: frontend · Категория: Технические вопросы

Компании: витринатв

Стек: JavaScript

> Пример ответа

Короткий ответ

Перехват ошибок в промисах выполняется через метод .catch() или второй аргумент .then(), а также через try/catch в связке с async/await. Главное отличие: try/catch работает только с синхронным кодом и await-выражениями, тогда как .catch() обрабатывает асинхронные rejection'ы на любом этапе цепочки промисов. Для async/await try/catch - это синтаксический сахар над .catch(), но он не ловит ошибки, возникшие после await вне блока.

Подробное объяснение

Промисы имеют два состояния завершения: fulfilled и rejected. Ошибка может возникнуть:

  • при создании промиса (в executor-функции);
  • в цепочке .then() - если предыдущий обработчик бросил исключение или вернул rejected-промис;
  • при использовании async/await - если await-выражение отклонено.

Механизм перехвата:

  • .catch(onRejected) - регистрирует обработчик rejection'а для всей цепочки, начиная с текущего промиса. Если ошибка не обработана в одном .catch(), она пробрасывается дальше по цепочке.
  • Второй аргумент .then(onFulfilled, onRejected) - ловит ошибку только на текущем шаге, не затрагивая последующие.
  • try/catch с async/await - работает как синхронный try/catch, но только для await-выражений внутри блока. Ошибки, возникающие в микрозадачах после await (например, в setTimeout), не перехватываются.

Ключевое отличие: try/catch не может обработать rejection, если промис создан, но не await'ится внутри блока. Например:

JAVASCRIPT
try {
Promise.reject(new Error('fail'));
} catch (e) {
// не сработает - ошибка асинхронная
}

Для async/await try/catch - это просто более читаемая альтернатива .catch(), но с нюансом: если внутри try есть несколько await, то catch поймает первую ошибку и прекратит выполнение блока.

На практике

Для цепочек промисов предпочтительнее .catch() в конце цепочки - это гарантирует обработку всех ошибок, включая те, что возникли в обработчиках. Для async/await - try/catch вокруг группы await-выражений, если нужна единая точка обработки. Если нужно обработать ошибку только на конкретном шаге, используйте второй аргумент .then() или отдельный try/catch вокруг одного await.

Важно: если в цепочке промисов нет .catch() и ошибка не обработана, в Node.js это приведёт к падению процесса (unhandledRejection), в браузере - к ошибке в консоли. Для async/await без try/catch - аналогично.

Пример кода

JAVASCRIPT
// Цепочка промисов
fetch('/api/data')
.then(res => res.json())
.then(data => process(data))
.catch(err => console.error('Ошибка в цепочке:', err));
// async/await с try/catch
async function load() {
try {
const res = await fetch('/api/data');
const data = await res.json();
return process(data);
} catch (err) {
console.error('Ошибка в async-функции:', err);
}
}
// Разница: try/catch не ловит асинхронную ошибку вне await
try {
Promise.reject(new Error('async fail'));
} catch (err) {
// не выполнится
}
// Правильно: использовать .catch() или await
try {
await Promise.reject(new Error('async fail'));
} catch (err) {
// выполнится
}

Как отвечать на собеседовании

Начните с краткого определения: .catch() - метод промиса, try/catch - синтаксическая конструкция. Затем объясните, что для async/await try/catch - это эквивалент .catch(), но с ограничением: он работает только с await-выражениями. Приведите пример, где try/catch не сработает для промиса без await. Упомяните про обработку ошибок в цепочках: .catch() в конце цепочки ловит всё, второй аргумент .then() - только текущий шаг. Завершите практическим советом: для сложных цепочек используйте .catch(), для читаемости - async/await с try/catch.

Что проверяет интервьюер

  • Понимание асинхронной природы промисов и отличие от синхронных исключений.
  • Умение объяснить, почему try/catch не ловит rejection без await.
  • Знание особенностей обработки ошибок в цепочках .then().
  • Понимание unhandledRejection и важность обязательной обработки ошибок.
  • Способность выбрать правильный инструмент в зависимости от контекста (цепочка vs async/await).

Типичные ошибки

  • Утверждение, что try/catch работает с любым промисом, даже без await.
  • Использование try/catch вокруг Promise.reject() в надежде поймать ошибку.
  • Забывание .catch() в конце цепочки - ошибка остаётся необработанной.
  • Попытка использовать второй аргумент .then() для обработки ошибок всей цепочки - он ловит только ошибки текущего шага.
  • Путаница между throw в синхронном коде и rejection в промисах - это разные механизмы, хотя async/await их объединяет.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью