Любой разработчик регулярно ловит ошибки. Сообщение в консоли вида TypeError: Cannot read property 'name' of undefined выглядит однотипно, но за каждым таким сообщением стоит конкретный тип объекта-ошибки, и от типа зависит, где именно искать причину.
В JavaScript встроенных типов ошибок не так много — меньше десятка, и почти все они унаследованы от общего базового класса Error. Разберёмся, кто из них когда возникает, чем отличаются compile-time и runtime ошибки, как их корректно ловить и когда имеет смысл объявлять собственный класс ошибки.
Базовый класс Error
Все встроенные ошибки — SyntaxError, TypeError, RangeError и остальные — это классы, наследующиеся от Error. У каждого экземпляра есть три главных поля:
- name — имя класса ("TypeError", "RangeError" и т. д.);
- message — человеческое описание;
- stack — стек вызовов на момент возникновения (нестандартизированное, но есть у всех движков).
Зная иерархию, можно одним catch ловить разные ошибки и принимать решения через instanceof — об этом в разделе про обработку.
Ошибки на этапе разбора кода
SyntaxError
Возникает, когда код не парсится: пропущена скобка, не закрыта строка, перепутан оператор. Главная особенность — до выполнения дело не доходит: движок ругается на этапе разбора, поэтому никакой try/catch такую ошибку не ловит (если только это не eval или JSON.parse над невалидной строкой).
const handler = () => console.log("hi") };
// SyntaxError: Unexpected token '}'
let my-var = 42;
// SyntaxError: Unexpected token '-'
Лечится в редакторе: линтер и подсветка синтаксиса вылавливают подобное ещё до запуска. Если перешли на TypeScript — компилятор не даст собрать проект, см. отдельную статью про TypeScript.
Ошибки во время выполнения
Эти ошибки бросаются уже после того, как файл успешно распарсился, и их можно ловить через try/catch.
ReferenceError
Возникает при обращении к переменной, которой не существует или которая недоступна в текущем скоупе.
console.log(score);
// ReferenceError: score is not defined
function inner() {
const local = 10;
}
inner();
console.log(local);
// ReferenceError: local is not defined
Ещё один частый случай — обращение к переменной до её объявления в зоне temporal dead zone для let/const:
console.log(price);
let price = 100;
// ReferenceError: Cannot access 'price' before initialization
TypeError
Бросается, когда операция применяется к значению неподходящего типа: вызов несуществующего метода, чтение свойства у undefined или null, попытка изменить замороженный объект.
const count = 5;
count.toUpperCase();
// TypeError: count.toUpperCase is not a function
const user = { name: "Anna" };
console.log(user.address.city);
// TypeError: Cannot read properties of undefined (reading 'city')
Самый часто встречающийся тип ошибки в реальной работе. Современные практики — опциональная цепочка user?.address?.city и оператор нулевого слияния ?? — во многом созданы именно для борьбы с TypeError.
RangeError
Бросается, когда числовой аргумент или индекс попал за допустимые границы операции. Стоит отдельно проговорить: обращение к несуществующему индексу массива — это не RangeError, оно тихо возвращает undefined. RangeError бросается только тогда, когда метод заранее объявил допустимый диапазон.
new Array(-1);
// RangeError: Invalid array length
(123).toFixed(101);
// RangeError: toFixed() digits argument must be between 0 and 100
new Date(NaN).toISOString();
// RangeError: Invalid time value
function recurse() { recurse(); }
recurse();
// RangeError: Maximum call stack size exceeded
Последний пример — переполнение стека — одна из самых громких разновидностей RangeError. В Firefox для этого случая исторически использовался отдельный InternalError, но это уже Firefox-only расширение.
URIError
Появляется, когда функции работы с URI получают строку, которую невозможно корректно обработать. Самый распространённый случай — кривой %-эскейп в decodeURIComponent.
decodeURIComponent("%E0%A4%A");
// URIError: URI malformed
decodeURIComponent("%");
// URIError: URI malformed
На практике встречается нечасто — обычно когда серверная часть отдала строку с экранированными символами в неполном виде или клиент сам что-то слепил конкатенацией.
AggregateError
Появился сравнительно недавно — для случая, когда нужно сообщить сразу о нескольких ошибках одной. Главный пользователь — метод Promise.any: если все переданные промисы отклонились, он бросает AggregateError со списком причин в поле errors.
const fast = Promise.reject(new Error("source A is down"));
const slow = Promise.reject(new Error("source B is down"));
try {
await Promise.any([fast, slow]);
} catch (err) {
console.log(err.name); // AggregateError
console.log(err.message); // All promises were rejected
console.log(err.errors); // [Error: 'source A is down', Error: 'source B is down']
}
Конструктор можно вызвать и руками, если своя операция возвращает несколько одновременных проблем:
throw new AggregateError(
[new TypeError("not a number"), new RangeError("out of bounds")],
"Validation failed"
);
Исторические и редкие
В чужом коде иногда мелькают ещё два класса — EvalError и InternalError. Оба сегодня в продакшене встречать почти не приходится, но знать о них полезно.
- EvalError — раньше бросался при некорректном использовании eval(). Со времён ECMAScript 2015 (ES6) современные движки его уже не кидают; класс остался только ради совместимости и иногда используется в самописных утилитах в стиле throw new EvalError(...).
- InternalError — нестандартное расширение Firefox для случаев типа «слишком глубокая рекурсия». В Chrome, Safari, Edge его нет — там вместо него летит RangeError: Maximum call stack size exceeded.
В новом коде эти классы не используют. Если встретили в логах — источник либо очень старый, либо Firefox-специфичный.
Как обрабатывать
Стандартная конструкция для работы с runtime-ошибками — try/catch/finally:
try {
const data = await fetchData();
process(data);
} catch (err) {
console.error("Не удалось обработать данные:", err);
} finally {
hideLoadingSpinner();
}
Блок finally выполняется всегда — и при успехе, и при выброшенной ошибке. Удобен для уборки: закрыть соединение, погасить лоадер, освободить ресурс.
В одном catch часто прилетают ошибки разных типов. Чтобы реагировать по-разному, помогает instanceof:
try {
validateAndSave(form);
} catch (err) {
if (err instanceof TypeError) {
showInlineError(err.message);
} else if (err instanceof RangeError) {
showInlineError("Значение выходит за допустимые границы");
} else {
reportToSentry(err);
throw err; // пробросить дальше всё, что не знаем
}
}
Принцип «пробросить дальше всё, что не умею обработать» — ключевой. Глотать незнакомые ошибки catch (err) { /* ничего */ } — верный способ скрыть баг и потом долго его ловить.
Свои классы ошибок
Для бизнес-логики удобно объявлять собственные классы. Это позволяет в catch различать «не пришёл ответ от сервера», «форма не прошла валидацию» и «превышена квота» одним и тем же instanceof.
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = "ValidationError";
this.field = field;
}
}
class ApiError extends Error {
constructor(message, status) {
super(message);
this.name = "ApiError";
this.status = status;
}
}
try {
await submit(form);
} catch (err) {
if (err instanceof ValidationError) {
highlightField(err.field);
} else if (err instanceof ApiError && err.status === 401) {
redirectToLogin();
} else {
throw err;
}
}
Главное правило — всегда проставлять this.name в конструкторе. Без него имя класса в логах и в err.toString() останется родительским Error, и читать стек становится неудобно.
Итог
JavaScript описывает не так уж много типов ошибок, и большинство из них появляются в коде каждый день: SyntaxError ловит редактор и линтер, ReferenceError и TypeError прилетают в консоль чаще всего, RangeError и URIError — реже и в более узких сценариях, AggregateError сопровождает Promise.any. Конструкция try/catch/finally и проверка через instanceof покрывают большинство задач, а собственные классы-наследники Error делают код предсказуемее, когда у бизнес-логики есть свои «ожидаемые» виды сбоев. Если эта тема в JS у вас в целом ещё в стадии освоения — начните с обзорной статьи про JavaScript.
Комментарии (0)