Виды ошибок и объект Error

ошибки≈ 7 минут

Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.

Когда что-то ломается, JavaScript не просто останавливается — он бросает объект, у которого есть вид, сообщение и след из вызовов. Научитесь читать эти три поля, и половина отладки превратится в чтение готового диагноза.

Разделы темы:·

Ошибка в JavaScript — такой же объект, как массив или дата. Его создаёт конструктор Error (или один из его наследников), и у него есть три поля, которые вы будете читать постоянно: name — вид ошибки, message — описание для человека, stack — след из вызовов, приведших к поломке.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Обратите внимание: создать объект ошибки — ещё не значит сломать программу. Пока он просто лежит в переменной, всё идёт своим чередом. Прерывает выполнение throw — и ровно то же самое делает движок, когда натыкается на недопустимую операцию, только объект ошибки он создаёт сам.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Error — общий предок, его бросают, когда ничего конкретнее не подходит. Остальные виды наследуются от него и говорят о характере беды: сам движок почти всегда выбирает специализированный конструктор, а не голый Error. Всего встроенных классов ошибок девять; пять из таблицы ниже закрывают почти всё, что встретится в повседневном коде, а EvalError, URIError, AggregateError и SuppressedError попадаются редко и в особых местах.

ВидКогда бросаетсяТипичный пример
ErrorОбщий случай, обычно бросает ваш собственный кодthrow new Error("Задача не найдена")
TypeErrorЗначение есть, но операция для него бессмысленнавызов не-функции, чтение свойства у undefined
ReferenceErrorИмени не существует ни в одной области видимостиопечатка в имени переменной
RangeErrorТип верный, но значение вне допустимого диапазонаtoFixed(200), бесконечная рекурсия
SyntaxErrorТекст не удаётся разобрать как код или как JSONJSON.parse со сломанной строкой

Проще всего запомнить границу между двумя самыми частыми: ReferenceError — «такого имени нет», TypeError — «имя есть, но с его значением так нельзя».

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Этот вид выпадает из общего ряда. Остальные ошибки случаются во время работы программы, а синтаксическая — до неё: движок сначала разбирает весь файл целиком. Если в нём забыта скобка, не выполнится ни одна строка — включая тот самый try, которым вы надеялись ошибку поймать.

Поэтому SyntaxError, который действительно ловят в приложениях, почти всегда прилетает из JSON.parse: там разбирается чужой текст, и происходит это уже во время работы программы.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Похожесть JSON на объектный литерал обманчива. Хвостовая запятая, одинарные кавычки, комментарий, undefined — всё это язык переживёт, а разбор JSON остановит.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Поле stack отвечает на вопрос «как мы сюда попали». Это обычный текст: в большинстве браузеров первой идёт строка с видом и сообщением, а дальше — список функций. Сверху та, внутри которой ошибка возникла, под ней — та, что её вызвала, и так вниз до верхнего уровня программы.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Читайте стек сверху вниз и ищите первую строку со своим файлом. Если верхние кадры принадлежат библиотеке, ошибка почти наверняка не в ней: это вы передали туда неподходящие данные, и первая ваша строка в списке и есть место, откуда стоит начинать разбор.

stack не описан в стандарте — формат у каждого движка свой. Читать глазами можно, разбирать регулярками не стоит.

Сообщения браузеров кажутся набором английских слов, но на деле их немного и они повторяются. Несколько формул стоит выучить сразу — они закрывают большую часть повседневных поломок.

Текст сообщенияЧто произошлоВид
x is not a functionВызвали то, что функцией не является: опечатка в имени метода или у этого типа его нетTypeError
Cannot read properties of undefinedСлева от точки оказался undefined: объект не пришёл, ключ назван иначе, ответ ещё не загрузилсяTypeError
x is not definedПеременной с таким именем нет: опечатка, забытое объявление или другой файлReferenceError
Assignment to constant variableПопытка присвоить заново тому, что объявлено через constTypeError
Maximum call stack size exceededРекурсия без условия выходаRangeError

Главный навык — смотреть не на то, что справа от точки, а на то, что слева. Сообщение «нельзя прочитать свойство items» жалуется вовсе не на items: виновато выражение перед ним, которое оказалось undefined.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Когда ошибку бросаете вы, берите не голый Error, а вид, который точнее описывает беду: TypeError — пришли данные не того типа, RangeError — тип верный, но значение вне допустимых границ. Тогда тот, кто ловит, сможет развести случаи по err.name или через instanceof, не разбирая текст сообщения.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Если встроенных видов не хватает, наследуйтесь от Error и задайте своё name. Такая ошибка остаётся обычной ошибкой — её ловит тот же catch, у неё есть stack, — но её легко отличить от чужой. Это вся механика, которая нужна здесь; следующая статья, «Свои классы ошибок», добавляет к ней cause, доменные поля и общего предка на семейство.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

  • Ошибка — это объект с полями name, message и stack; программу прерывает не создание объекта, а throw.
  • TypeError — значение не того типа, ReferenceError — имени не существует, RangeError — значение вне границ, SyntaxError — текст не разбирается.
  • Синтаксическую ошибку в собственном файле поймать нельзя: код не запускается вовсе. Ловят обычно ту, что пришла из JSON.parse.
  • Стек читают сверху вниз: наверху место броска, ниже — цепочка вызовов; формат текста зависит от браузера.
  • Сообщение про нечитаемое свойство указывает на то, что слева от точки; свои ошибки бросайте подходящим конструктором.

1/3Переменная counter нигде не объявлена. Какой будет вид ошибки у console.log(counter)?

2/3Что окажется в err.name, если поймать ошибку от JSON.parse("{name: Аня}")?

3/3Где в стеке вызовов искать место, в котором ошибка возникла?

Определить вид ошибки

Напишите функцию errorName(fn): она вызывает переданную функцию и возвращает name пойманной ошибки, а если ошибки не было — строку "ok".

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Вся суть — в порядке строк внутри try: return "ok" должен стоять после вызова, иначе он выполнится раньше, чем ошибка успеет случиться.

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Возвращать именно err.name, а не err.message, здесь важно: имя вида одинаково во всех браузерах, а текст сообщения у каждого движка свой.

Бросить подходящий вид

Функция getItem(arr, index) возвращает элемент массива. Если первый аргумент не массив — бросьте TypeError, если индекс выходит за границы — RangeError. Сообщения придумайте свои.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Проверки идут по порядку: сначала тип, потом диапазон — проверять границы у не-массива бессмысленно.

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Строка тоже индексируется, поэтому без явной проверки Array.isArray вызов getItem("строка", 0) молча вернул бы букву вместо ошибки.