Урок по треку JavaScript 6 мин

Ошибки за 6 минут

try/catch/finally, свои классы ошибок, cause, асинхронные и необработанные ошибки.

Ошибки в JavaScript это обычные объекты, которые можно бросить, поймать, расширить и передать дальше. На собесе проверяют не синтаксис try/catch, а понимание: что ловится, что нет, и как строить обработку ошибок в приложении.

Встроенные типы

ТипКогда возникает
Errorбазовый тип, от него наследуются остальные
TypeErrorоперация над значением не того типа: вызов не функции, чтение свойства undefined
ReferenceErrorобращение к необъявленной переменной или к let/const в TDZ
SyntaxErrorошибка разбора кода, JSON.parse с битой строкой
RangeErrorзначение вне допустимого диапазона: new Array(-1), переполнение стека рекурсией
AggregateErrorнесколько ошибок сразу, например из Promise.any

У ошибки есть name, message, нестандартный, но поддерживаемый всеми движками stack и, с ES2022, cause.

try / catch / finally

try {
  const data = JSON.parse(text);
} catch (err) {
  console.error('Битый JSON', err);
} finally {
  hideSpinner(); // выполняется всегда
}
  • catch можно писать без параметра: catch { ... } (ES2019).
  • finally выполняется при любом исходе, даже после return в try или catch.
  • Если finally сам делает return, он перекрывает и возвращаемое значение, и брошенную ошибку. Так делать не надо.

Что try/catch не ловит

  • Синтаксические ошибки в самом файле: код не запустится вообще.
  • Асинхронные ошибки без await. try { setTimeout(() => { throw e }) } catch {} не поймает: колбэк выполнится позже, в другой задаче. Промис без await тоже: try { fetchData() } catch {} пропустит отклонение.

Можно бросить что угодно, но не нужно

throw 'текст' и throw { code: 1 } синтаксически допустимы, но у таких значений нет стека и instanceof Error ложен. Бросайте экземпляры Error или наследников. В catch с TypeScript переменная имеет тип unknown, именно потому что бросить могли что угодно.

Свои классы ошибок

class HttpError extends Error {
  constructor(status, message, options) {
    super(message, options);  // options может содержать cause
    this.name = 'HttpError';
    this.status = status;
  }
}

try {
  throw new HttpError(404, 'Не найдено');
} catch (err) {
  if (err instanceof HttpError && err.status === 404) showNotFound();
  else throw err; // чужие ошибки пробрасываем дальше
}

Правило: ловите только то, что умеете обработать, остальное пробрасывайте. Пустой catch {} прячет баги.

Error.cause

Когда вы оборачиваете низкоуровневую ошибку в понятную, сохраняйте исходную:

try {
  await db.query(sql);
} catch (err) {
  throw new Error('Не удалось сохранить заказ', { cause: err });
}

Исходная ошибка с её стеком будет доступна в err.cause, DevTools и Node показывают цепочку.

Глобальные обработчики

Последняя линия обороны, чтобы ничего не пропало молча:

  • браузер: window.addEventListener('error', ...) для неперехваченных исключений и unhandledrejection для промисов;
  • Node.js: process.on('uncaughtException') и process.on('unhandledRejection'); после uncaughtException процесс в непредсказуемом состоянии, его правильно логировать и перезапускать.

Сюда обычно подключают отправку в Sentry или аналог.

Ошибки в React

Ошибки при рендере ловят Error Boundary (классовый компонент с getDerivedStateFromError/componentDidCatch). Они не ловят ошибки в обработчиках событий и асинхронном коде: там обычный try/catch.

Частые ошибки

  • Пустой catch, который глотает всё.
  • try/catch вокруг асинхронного вызова без await.
  • throw 'строка' вместо throw new Error('...').
  • Потеря исходной ошибки при обёртке: забыли cause.
  • return в finally.

Что спросят на собесе

  • Какие встроенные типы ошибок вы знаете и когда возникает каждый.
  • Как работает finally и что будет, если в нём return.
  • Почему try/catch не ловит ошибку в setTimeout.
  • Как сделать свой класс ошибки и зачем.
  • Как устроена обработка ошибок в вашем приложении: где ловите, куда логируете.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.