Ошибки в 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. - Как сделать свой класс ошибки и зачем.
- Как устроена обработка ошибок в вашем приложении: где ловите, куда логируете.