Ошибки — это объекты со своим типом
Когда в JavaScript выбрасывается ошибка, вам прилетает не просто строка с текстом, а полноценный объект. У этого объекта есть свой тип (конструктор) и несколько стандартных свойств: name, message и stack.
err.name — это короткая метка вроде "TypeError". В err.message лежит понятное человеку описание. А err instanceof TypeError подскажет, к какому конкретно классу относится ошибка. Понимать тип ошибки важно: так вы сразу видите, в чём дело — опечатка, негодное значение или код, который вообще не распарсился.
Всего в JavaScript семь встроенных типов ошибок. Три из них будут встречаться вам постоянно, остальные четыре — изредка.
SyntaxError: код не распарсился
SyntaxError означает, что JavaScript даже не смог прочитать ваш код. По сути это не ошибка времени выполнения — движок падает ещё на этапе парсинга, до того как выполнится хоть одна строка. Перехватить SyntaxError через try/catch в том же файле не получится, потому что весь файл отбраковывается целиком.
function greet(name {
return "hi, " + name;
}
// SyntaxError: Unexpected token '{'
Пропущенная скобка, лишняя запятая, return вне функции — любое нарушение грамматики языка приводит к такой ошибке. Лечится это всегда одинаково: правим исходник. Единственный случай, когда SyntaxError реально можно перехватить, — это парсинг во время выполнения, например через JSON.parse:
JSON.parse получает строку уже во время выполнения, поэтому его синтаксические ошибки можно перехватить. А вот ошибки в самих исходниках — нет.
ReferenceError: такого имени не существует
ReferenceError в javascript возникает, когда вы обращаетесь к переменной, которая не объявлена ни в одной области видимости, доступной текущему коду.
В девяти случаях из десяти это банальная опечатка (totl вместо total). Оставшиеся 10% — проблемы с областью видимости: вы пытаетесь обратиться к переменной, объявленной в другой функции или модуле.
Есть ещё одна, менее очевидная причина — временная мёртвая зона (temporal dead zone). Объявления let и const существуют с самого начала блока, но обратиться к ним до строки с объявлением нельзя:
К моменту вызова console.log(x) переменная x уже существует как привязка, но ещё не проинициализирована. Отсюда и ошибка обращения. Чтобы починить — перенесите обращение ниже объявления.
TypeError в JavaScript: значение не того типа
TypeError означает, что значение-то есть, но оно не того сорта, который ожидает операция. Попытка вызвать то, что не является функцией, чтение свойства у null или undefined, присваивание в const — всё это TypeError.
«Cannot read properties of null (reading 'name')» — пожалуй, самая частая ошибка в JavaScript, которую ловят все без исключения. Лечится это либо гарантией того, что значение точно существует, либо опциональной цепочкой при обращении к свойству: user?.name.
Другие разновидности TypeError:
Попытка вызвать число как функцию, переприсвоить const, дёрнуть несуществующий метод — всё это значит, что значение оказалось не того типа, которого ждала операция.
RangeError: число вышло за допустимые границы
RangeError возникает, когда число само по себе валидное, но выходит за пределы, допустимые для конкретной операции.
Классический виновник — бесконечная рекурсия, от которой переполняется стек вызовов:
«Maximum call stack size exceeded» почти всегда означает, что функция вызывает саму себя без базового случая, либо две функции зациклились во взаимном вызове.
URIError и EvalError: редкие гости
URIError возникает при работе с функциями для обработки URI (encodeURI, decodeURIComponent и им подобными), когда на вход подаётся некорректная строка:
EvalError — это атавизм. Современные движки JavaScript его уже не выбрасывают ни при каких условиях, а сам конструктор оставлен только ради обратной совместимости. Создать такую ошибку руками можно, но в реальном коде вы её не встретите.
Цепочка наследования ошибок
Все эти типы наследуются от базового класса Error. А значит, для любого из них err instanceof Error вернёт true — это удобно, когда нужен универсальный catch:
Блок catch ловит всё подряд — в том числе и не-ошибки, если кто-то написал throw "oops". И вот эта последняя ветка как раз важна. Прежде чем обращаться с перехваченным значением как с объектом ошибки, всегда сужайте тип через instanceof.
Как выбросить ошибку в JavaScript вручную
Вы можете сами выбросить любой из встроенных типов ошибок, когда ваш код обнаружил проблему. Главное — подобрать тип под конкретный сбой:
Подбирать тип под конкретный сбой — это не просто косметика. Благодаря этому вызывающий код может писать точечные catch-блоки, а не разбирать текст сообщения об ошибке.
Свой класс ошибки в JavaScript
Когда встроенных типов не хватает, наследуемся от Error:
Запомните две вещи: обязательно вызывайте super(message), чтобы базовый Error корректно проинициализировался, и выставляйте this.name — тогда в логах будет правильная метка. А дополнительные поля вроде field позволяют вызывающему коду реагировать на конкретные сценарии сбоев, не разбирая строки сообщения.
Дальше: консоль и DevTools
Знать типы ошибок JavaScript — это только половина дела. Вторая половина — уметь читать стектрейс и заглядывать в состояние программы прямо во время её работы. DevTools в браузере (и отладчик в Node) превращают «упало, и я не понимаю почему» в пару секунд осмотра. Об этом и поговорим дальше.
Часто задаваемые вопросы
Какие встроенные типы ошибок есть в JavaScript?
В JavaScript их семь: базовый Error, а также SyntaxError, ReferenceError, TypeError, RangeError, URIError и EvalError. Каждый — это конструктор, который создаёт объект ошибки со свойствами name, message и stack. На практике чаще всего приходится иметь дело с SyntaxError, ReferenceError и TypeError.
В чём разница между SyntaxError и TypeError?
SyntaxError означает, что код вообще не является валидным JavaScript — движок не может его распарсить. TypeError — это когда код нормально распарсился, но во время выполнения произошло что-то недопустимое: например, вы пытаетесь вызвать не функцию или обратиться к свойству null. Синтаксические ошибки ломают весь скрипт целиком, а ошибки типов срабатывают только тогда, когда проблемная строка действительно выполняется.
Когда возникает ReferenceError в JavaScript?
Когда вы обращаетесь к имени, которое нигде не объявлено, либо к переменной let/const в её temporal dead zone — то есть до строки с объявлением. Самая частая причина — банальная опечатка: consoel.log(x) выбросит ReferenceError: consoel is not defined. Первым делом проверяйте написание и область видимости.
Можно ли создавать свои типы ошибок?
Да, достаточно унаследоваться от встроенного класса Error: class ValidationError extends Error { }. В конструкторе обязательно задайте this.name, чтобы в логах и в блоках catch можно было отличить одну ошибку от другой. Свои классы ошибок особенно удобны, когда разные ситуации требуют разной обработки.