Полное руководство по обработке ошибок в JavaScript
Опубликовано: 2022-01-24Закон Мерфи гласит, что все, что может пойти не так, в конечном итоге пойдет не так. Это слишком хорошо применимо в мире программирования. Если вы создаете приложение, скорее всего, вы создадите ошибки и другие проблемы. Ошибки в JavaScript — одна из таких распространенных проблем!
Успех программного продукта зависит от того, насколько хорошо его создатели смогут решить эти проблемы до того, как навредят своим пользователям. А JavaScript из всех языков программирования печально известен своим средним дизайном обработки ошибок.
Если вы создаете приложение JavaScript, велика вероятность того, что в тот или иной момент вы запутаетесь с типами данных. Если это не так, то вы можете в конечном итоге заменить undefined на null или оператор тройного равенства ( ===
) на оператор двойного равенства ( ==
).
Только человеку свойственно ошибаться. Вот почему мы покажем вам все, что вам нужно знать об обработке ошибок в JavaScript.
Эта статья расскажет вам об основных ошибках в JavaScript и объяснит различные ошибки, с которыми вы можете столкнуться. Затем вы узнаете, как выявлять и исправлять эти ошибки. Есть также несколько советов по эффективной обработке ошибок в производственной среде.
Без лишних слов, давайте начнем!
Что такое ошибки JavaScript?
Ошибки в программировании относятся к ситуациям, которые не позволяют программе нормально работать. Это может произойти, когда программа не знает, как справиться с выполняемой задачей, например, при попытке открыть несуществующий файл или обратиться к конечной точке веб-API при отсутствии подключения к сети.
Эти ситуации вынуждают программу выдавать пользователю ошибки, заявляя, что она не знает, что делать дальше. Программа собирает как можно больше информации об ошибке, а затем сообщает, что не может двигаться дальше.
Умные программисты пытаются предсказать и учесть эти сценарии, чтобы пользователю не приходилось самостоятельно разбираться в технических сообщениях об ошибках, таких как «404». Вместо этого они показывают гораздо более понятное сообщение: «Страница не найдена».
Ошибки в JavaScript — это объекты, отображаемые всякий раз, когда возникает ошибка программирования. Эти объекты содержат достаточную информацию о типе ошибки, инструкции, вызвавшей ошибку, и трассировке стека на момент возникновения ошибки. JavaScript также позволяет программистам создавать пользовательские ошибки для предоставления дополнительной информации при устранении неполадок.
Свойства ошибки
Теперь, когда определение ошибки JavaScript ясно, пришло время углубиться в детали.
Ошибки в JavaScript содержат определенные стандартные и настраиваемые свойства, которые помогают понять причину и последствия ошибки. По умолчанию ошибки в JavaScript содержат три свойства:
- message : Строковое значение, которое содержит сообщение об ошибке.
- name : тип возникшей ошибки (мы углубимся в это в следующем разделе).
- stack : трассировка стека кода, выполняемого при возникновении ошибки.
Кроме того, ошибки также могут содержать такие свойства, как columnNumber, lineNumber, fileName и т. д., чтобы лучше описать ошибку. Однако эти свойства не являются стандартными и могут присутствовать или отсутствовать в каждом объекте ошибки, сгенерированном вашим приложением JavaScript.
Понимание трассировки стека
Трассировка стека — это список вызовов методов, в которых находилась программа, когда возникает такое событие, как исключение или предупреждение. Вот как выглядит образец трассировки стека, сопровождаемый исключением:

Как видите, он начинается с вывода имени ошибки и сообщения, за которым следует список вызываемых методов. Каждый вызов метода указывает местоположение его исходного кода и строку, в которой он был вызван. Вы можете использовать эти данные для навигации по кодовой базе и определения фрагмента кода, вызывающего ошибку.
Этот список методов организован в виде стопки. Он показывает, где ваше исключение было впервые сгенерировано и как оно распространялось через стекированные вызовы методов. Реализация перехвата для исключения не позволит ему распространиться по стеку и привести к сбою вашей программы. Однако вы можете захотеть оставить фатальные ошибки необработанными, чтобы преднамеренно завершить работу программы в некоторых сценариях.
Ошибки и исключения
Большинство людей обычно считают ошибки и исключения одним и тем же. Однако важно отметить небольшую, но принципиальную разницу между ними.
Чтобы понять это лучше, давайте возьмем быстрый пример. Вот как вы можете определить ошибку в JavaScript:
const wrongTypeError = TypeError("Wrong type found, expected character")
И вот как объект wrongTypeError
становится исключением:
throw wrongTypeError
Тем не менее, большинство людей склонны использовать сокращенную форму, которая определяет объекты ошибок при их выдаче:
throw TypeError("Wrong type found, expected character")
Это стандартная практика. Однако это одна из причин, по которой разработчики часто путают исключения и ошибки. Поэтому знание основ жизненно важно, даже если вы используете сокращения для быстрого выполнения своей работы.
Типы ошибок в JavaScript
В JavaScript существует ряд предопределенных типов ошибок. Они автоматически выбираются и определяются средой выполнения JavaScript всякий раз, когда программист явно не обрабатывает ошибки в приложении.
В этом разделе вы познакомитесь с некоторыми наиболее распространенными типами ошибок в JavaScript и поймете, когда и почему они возникают.
RangeError
Ошибка RangeError возникает, когда переменной присваивается значение, выходящее за пределы допустимого диапазона значений. Обычно это происходит при передаче значения в качестве аргумента функции, и данное значение не лежит в диапазоне параметров функции. Иногда это может быть сложно исправить при использовании плохо документированных сторонних библиотек, поскольку вам нужно знать диапазон возможных значений для аргументов, чтобы передать правильное значение.
Некоторые из распространенных сценариев, в которых возникает RangeError:
- Попытка создать массив недопустимых длин с помощью конструктора Array.
- Передача неверных значений числовым методам, таким как
toExponential()
,toPrecision()
,toFixed()
и т. д. - Передача недопустимых значений строковым функциям, таким как
normalize()
.
ReferenceError
Ошибка ReferenceError возникает, когда что-то не так со ссылкой на переменную в вашем коде. Возможно, вы забыли определить значение переменной перед ее использованием или пытались использовать недоступную переменную в своем коде. В любом случае просмотр трассировки стека дает достаточно информации, чтобы найти и исправить ссылку на переменную, вызвавшую ошибку.
Некоторые из распространенных причин возникновения ReferenceErrors:
- Опечатка в имени переменной.
- Попытка доступа к блочным переменным за пределами их областей.
- Ссылка на глобальную переменную из внешней библиотеки (например, $ из jQuery) перед ее загрузкой.
Ошибка синтаксиса
Эти ошибки являются одними из самых простых для исправления, поскольку они указывают на ошибку в синтаксисе кода. Поскольку JavaScript — это язык сценариев, который интерпретируется, а не компилируется, они вызываются, когда приложение выполняет сценарий, содержащий ошибку. В случае компилируемых языков такие ошибки выявляются во время компиляции. Таким образом, двоичные файлы приложения не создаются, пока они не будут исправлены.
Вот некоторые из распространенных причин возникновения SyntaxErrors:
- Отсутствуют кавычки
- Не хватает закрывающих скобок
- Неправильное выравнивание фигурных скобок или других символов
Хорошей практикой является использование инструмента linting в вашей среде IDE для выявления таких ошибок до того, как они попадут в браузер.
Ошибка типа
TypeError — одна из самых распространенных ошибок в приложениях JavaScript. Эта ошибка возникает, когда какое-то значение не соответствует определенному ожидаемому типу. Некоторые из распространенных случаев, когда это происходит:
- Вызов объектов, не являющихся методами.
- Попытка доступа к свойствам пустых или неопределенных объектов
- Обработка строки как числа или наоборот
Есть гораздо больше возможностей, где может произойти TypeError. Позже мы рассмотрим некоторые известные экземпляры и узнаем, как их исправить.
Внутренняя ошибка
Тип InternalError используется, когда в механизме выполнения JavaScript возникает исключение. Это может указывать или не указывать на проблему с вашим кодом.
Чаще всего InternalError возникает только в двух случаях:
- Когда патч или обновление среды выполнения JavaScript содержит ошибку, вызывающую исключения (это случается очень редко).
- Когда ваш код содержит объекты, которые слишком велики для движка JavaScript (например, слишком много случаев переключения, слишком большие инициализаторы массива, слишком много рекурсии)
Наиболее подходящий подход к устранению этой ошибки — определить причину с помощью сообщения об ошибке и, если возможно, изменить логику приложения, чтобы устранить внезапный всплеск нагрузки на движок JavaScript.
URIError
URIError возникает, когда глобальная функция обработки URI, такая как decodeURIComponent
, используется незаконно. Обычно это указывает на то, что параметр, переданный вызову метода, не соответствует стандартам URI и, следовательно, не был должным образом проанализирован методом.
Диагностировать эти ошибки обычно несложно, так как вам нужно только изучить аргументы в пользу порока развития.
EvalError
EvalError возникает, когда возникает ошибка при вызове функции eval()
. Функция eval()
используется для выполнения кода JavaScript, хранящегося в строках. Однако, поскольку использование функции eval()
настоятельно не рекомендуется из-за проблем с безопасностью, а текущие спецификации ECMAScript больше не вызывают класс EvalError
, этот тип ошибки существует просто для обеспечения обратной совместимости с устаревшим кодом JavaScript.
Если вы работаете со старой версией JavaScript, вы можете столкнуться с этой ошибкой. В любом случае лучше исследовать код, выполняемый при вызове функции eval()
, на наличие исключений.
Создание пользовательских типов ошибок
Хотя JavaScript предлагает адекватный список классов типов ошибок для большинства сценариев, вы всегда можете создать новый тип ошибок, если список не удовлетворяет вашим требованиям. Основа такой гибкости заключается в том, что JavaScript позволяет вам бросать что угодно буквально с помощью команды throw
.
Итак, технически эти утверждения полностью законны:
throw 8 throw "An error occurred"
Однако создание примитивного типа данных не предоставляет сведений об ошибке, таких как ее тип, имя или сопутствующая трассировка стека. Чтобы исправить это и стандартизировать процесс обработки ошибок, был предоставлен класс Error
. Также не рекомендуется использовать примитивные типы данных при генерации исключений.
Вы можете расширить класс Error
, чтобы создать собственный класс ошибок. Вот базовый пример того, как вы можете это сделать:
class ValidationError extends Error { constructor(message) { super(message); this.name = "ValidationError"; } }
И вы можете использовать его следующим образом:
throw ValidationError("Property not found: name")
И затем вы можете идентифицировать его с помощью ключевого слова instanceof
:
try { validateForm() // code that throws a ValidationError } catch (e) { if (e instanceof ValidationError) // do something else // do something else }
10 самых распространенных ошибок в JavaScript
Теперь, когда вы понимаете распространенные типы ошибок и то, как создавать свои собственные, пришло время взглянуть на некоторые из наиболее распространенных ошибок, с которыми вы столкнетесь при написании кода JavaScript.
1. Неперехваченная ошибка диапазона
Эта ошибка возникает в Google Chrome в нескольких различных сценариях. Во-первых, это может произойти, если вы вызываете рекурсивную функцию, и она не завершается. Вы можете проверить это самостоятельно в консоли разработчика Chrome:

Поэтому, чтобы решить такую ошибку, убедитесь, что вы правильно определили пограничные случаи вашей рекурсивной функции. Другая причина, по которой возникает эта ошибка, заключается в том, что вы передали значение, выходящее за пределы диапазона параметра функции. Вот пример:

Сообщение об ошибке обычно указывает, что не так с вашим кодом. Как только вы внесете изменения, проблема будет решена.

2. Uncaught TypeError: Невозможно установить свойство
Эта ошибка возникает, когда вы устанавливаете свойство для неопределенной ссылки. Вы можете воспроизвести проблему с помощью этого кода:
var list list.count = 0
Вот результат, который вы получите:

Чтобы исправить эту ошибку, инициализируйте ссылку значением перед доступом к ее свойствам. Вот как это выглядит после исправления:

3. Uncaught TypeError: невозможно прочитать свойство
Это одна из наиболее часто встречающихся ошибок в JavaScript. Эта ошибка возникает, когда вы пытаетесь прочитать свойство или вызвать функцию для неопределенного объекта. Вы можете очень легко воспроизвести его, запустив следующий код в консоли разработчика Chrome:
var func func.call()
Вот результат:

Неопределенный объект является одной из многих возможных причин этой ошибки. Другой важной причиной этой проблемы может быть неправильная инициализация состояния при отрисовке пользовательского интерфейса. Вот реальный пример из приложения React:
import React, { useState, useEffect } from "react"; const CardsList = () => { const [state, setState] = useState(); useEffect(() => { setTimeout(() => setState({ items: ["Card 1", "Card 2"] }), 2000); }, []); return ( <> {state.items.map((item) => ( <li key={item}>{item}</li> ))} </> ); }; export default CardsList;
Приложение запускается с пустым контейнером состояния и получает некоторые элементы с задержкой в 2 секунды. Задержка введена для имитации сетевого вызова. Даже если ваша сеть очень быстрая, вы все равно столкнетесь с небольшой задержкой, из-за которой компонент отобразится хотя бы один раз. Если вы попытаетесь запустить это приложение, вы получите следующую ошибку:

Это связано с тем, что во время рендеринга контейнер состояния не определен; таким образом, на нем не существует items
собственности. Исправить эту ошибку несложно. Вам просто нужно указать начальное значение по умолчанию для контейнера состояния.
// ... const [state, setState] = useState({items: []}); // ...
Теперь, после установленной задержки, ваше приложение покажет аналогичный вывод:

Точное исправление в вашем коде может быть другим, но суть здесь в том, чтобы всегда правильно инициализировать ваши переменные перед их использованием.
4. TypeError: 'undefined' не является объектом
Эта ошибка возникает в Safari, когда вы пытаетесь получить доступ к свойствам неопределенного объекта или вызвать метод для него. Вы можете запустить тот же код, что и выше, чтобы воспроизвести ошибку самостоятельно.

Решение этой ошибки такое же — убедитесь, что вы правильно инициализировали свои переменные и они не являются неопределенными при доступе к свойству или методу.
5. TypeError: null не является объектом
Это, опять же, похоже на предыдущую ошибку. Это происходит в Safari, и единственная разница между этими двумя ошибками заключается в том, что эта возникает, когда объект, к свойству или методу которого осуществляется доступ, имеет значение null
вместо undefined
. Вы можете воспроизвести это, запустив следующий фрагмент кода:
var func = null func.call()
Вот результат, который вы получите:

Поскольку null
— это значение, явно установленное для переменной и не назначенное автоматически JavaScript. Эта ошибка может возникнуть только в том случае, если вы пытаетесь получить доступ к переменной, для которой вы сами установили значение null
. Итак, вам нужно пересмотреть свой код и проверить, правильна ли написанная вами логика или нет.
6. TypeError: невозможно прочитать свойство «длина»
Эта ошибка возникает в Chrome при попытке прочитать длину null
или undefined
объекта. Причина этой проблемы аналогична предыдущим проблемам, но она возникает довольно часто при обработке списков; поэтому он заслуживает особого упоминания. Вот как вы можете воспроизвести проблему:

Однако в более новых версиях Chrome об этой ошибке сообщается как Uncaught TypeError: Cannot read properties of undefined
. Вот как это выглядит сейчас:

Исправление, опять же, заключается в том, чтобы убедиться, что объект, длина которого вы пытаетесь получить доступ, существует и не имеет значение null
.
7. TypeError: 'undefined' не является функцией
Эта ошибка возникает, когда вы пытаетесь вызвать метод, который не существует в вашем скрипте, или он существует, но на него нельзя сослаться в контексте вызова. Эта ошибка обычно возникает в Google Chrome, и вы можете решить ее, проверив строку кода, выдающую ошибку. Если вы нашли опечатку, исправьте ее и проверьте, решает ли она вашу проблему.
Если вы использовали в своем коде самоссылающееся ключевое слово this
, эта ошибка может возникнуть, если this
не привязано должным образом к вашему контексту. Рассмотрим следующий код:
function showAlert() { alert("message here") } document.addEventListener("click", () => { this.showAlert(); })
Если вы выполните приведенный выше код, он выдаст ошибку, которую мы обсуждали. Это происходит потому, что анонимная функция, переданная в качестве прослушивателя событий, выполняется в контексте document
.
Напротив, функция showAlert
определена в контексте window
.
Чтобы решить эту проблему, вы должны передать правильную ссылку на функцию, связав ее с методом bind()
:
document.addEventListener("click", this.showAlert.bind(this))
8. ReferenceError: событие не определено
Эта ошибка возникает при попытке доступа к ссылке, не определенной в вызывающей области. Обычно это происходит при обработке событий, поскольку они часто предоставляют вам ссылку, называемую event
в функции обратного вызова. Эта ошибка может возникнуть, если вы забыли определить аргумент события в параметрах вашей функции или написали его с ошибкой.
Эта ошибка может не возникать в Internet Explorer или Google Chrome (поскольку IE предлагает глобальную переменную события, а Chrome автоматически прикрепляет переменную события к обработчику), но она может возникнуть в Firefox. Поэтому рекомендуется следить за такими небольшими ошибками.

9. TypeError: присваивание постоянной переменной
Это ошибка, которая возникает из-за невнимательности. Если вы попытаетесь присвоить константной переменной новое значение, то получите такой результат:

Хотя сейчас это кажется легко исправить, представьте сотни таких объявлений переменных, и одно из них ошибочно определено как const
вместо let
! В отличие от других языков сценариев, таких как PHP, разница между стилем объявления констант и переменных в JavaScript минимальна. Поэтому рекомендуется в первую очередь проверять свои объявления, когда вы сталкиваетесь с этой ошибкой. Вы также можете столкнуться с этой ошибкой, если забудете , что указанная ссылка является константой, и используете ее как переменную. Это указывает либо на небрежность, либо на ошибку в логике вашего приложения. Обязательно проверьте это при попытке решить эту проблему.
10. (неизвестно): ошибка сценария
Ошибка скрипта возникает, когда сторонний скрипт отправляет ошибку в ваш браузер. За этой ошибкой следует (неизвестно), потому что сторонний скрипт принадлежит к другому домену, чем ваше приложение. Браузер скрывает другие данные, чтобы предотвратить утечку конфиденциальной информации из стороннего скрипта.
Вы не можете решить эту ошибку, не зная полных деталей. Вот что вы можете сделать, чтобы получить больше информации об ошибке:
- Добавьте атрибут
crossorigin
в тег скрипта. - Установите правильный заголовок
Access-Control-Allow-Origin
на сервере, на котором размещен скрипт. - [Необязательно] Если у вас нет доступа к серверу, на котором размещен скрипт, вы можете рассмотреть возможность использования прокси-сервера для передачи вашего запроса на сервер и обратно клиенту с правильными заголовками.
Как только вы сможете получить доступ к деталям ошибки, вы можете приступить к устранению проблемы, которая, вероятно, будет связана либо со сторонней библиотекой, либо с сетью.
Как определить и предотвратить ошибки в JavaScript
Хотя описанные выше ошибки являются наиболее распространенными и частыми в JavaScript, вы столкнетесь с тем, что нескольких примеров никогда не будет достаточно. Крайне важно понимать, как обнаруживать и предотвращать ошибки любого типа в приложении JavaScript при его разработке. Вот как вы можете обрабатывать ошибки в JavaScript.
Выбрасывать и перехватывать ошибки вручную
Наиболее фундаментальный способ обработки ошибок, которые были вызваны либо вручную, либо средой выполнения, — это их перехват. Как и большинство других языков, JavaScript предлагает набор ключевых слов для обработки ошибок. Очень важно подробно изучить каждый из них, прежде чем приступить к обработке ошибок в своем приложении JavaScript.
бросать
Первое и самое основное ключевое слово набора — throw
. Как видно, ключевое слово throw используется для генерации ошибок для создания исключений в среде выполнения JavaScript вручную. Мы уже обсуждали это ранее в статье, и вот суть значения этого ключевого слова:
- Вы можете
throw
что угодно, включая числа, строки и объектыError
. - Однако не рекомендуется выбрасывать примитивные типы данных, такие как строки и числа, поскольку они не несут отладочной информации об ошибках.
- Пример:
throw TypeError("Please provide a string")
пытаться
Ключевое слово try
используется для указания того, что блок кода может вызвать исключение. Его синтаксис:
try { // error-prone code here }
Важно отметить, что блок catch
всегда должен следовать за блоком try
для эффективной обработки ошибок.
ловить
Ключевое слово catch
используется для создания блока catch. Этот блок кода отвечает за обработку ошибок, которые перехватывает замыкающий блок try
. Вот его синтаксис:
catch (exception) { // code to handle the exception here }
И вот как вы реализуете блоки try
и catch
вместе:
try { // business logic code } catch (exception) { // error handling code }
В отличие от C++ или Java, вы не можете добавлять несколько блоков catch
к блоку try
в JavaScript. Это означает, что вы не можете сделать это:
try { // business logic code } catch (exception) { if (exception instanceof TypeError) { // do something } } catch (exception) { if (exception instanceof RangeError) { // do something } }
Вместо этого вы можете использовать оператор if...else
или оператор switch case внутри одного блока catch для обработки всех возможных случаев ошибок. Это будет выглядеть так:
try { // business logic code } catch (exception) { if (exception instanceof TypeError) { // do something } else if (exception instanceof RangeError) { // do something else } }
в конце концов
Ключевое слово finally
используется для определения блока кода, который запускается после обработки ошибки. Этот блок выполняется после блоков try и catch.
Кроме того, блок finally будет выполняться независимо от результата двух других блоков. Это означает, что даже если блок catch не может полностью обработать ошибку или в блоке catch выдается ошибка, интерпретатор выполнит код в блоке finally до того, как программа вылетит.
Чтобы считаться действительным, за блоком try в JavaScript должен следовать либо блок catch, либо блок finally. Без любого из них интерпретатор вызовет SyntaxError. Поэтому при обработке ошибок следите за своими блоками try хотя бы с одним из них.
Глобальная обработка ошибок с помощью метода onerror()
Метод onerror()
доступен для всех элементов HTML для обработки любых ошибок, которые могут с ними произойти. Например, если тег img
не может найти изображение с указанным URL-адресом, он запускает свой метод onerror, чтобы позволить пользователю обработать ошибку.
Как правило, вы указываете другой URL-адрес изображения в вызове onerror для тега img
, к которому можно вернуться. Вот как вы можете сделать это с помощью JavaScript:
const image = document.querySelector("img") image.onerror = (event) => { console.log("Error occurred: " + event) }
Однако вы можете использовать эту функцию для создания глобального механизма обработки ошибок для вашего приложения. Вот как это сделать:
window.onerror = (event) => { console.log("Error occurred: " + event) }
С помощью этого обработчика событий вы можете избавиться от нескольких блоков try...catch
, находящихся в вашем коде, и централизовать обработку ошибок вашего приложения, аналогичную обработке событий. Вы можете прикрепить к окну несколько обработчиков ошибок, чтобы сохранить принцип единой ответственности из принципов проектирования SOLID. Интерпретатор будет циклически перебирать все обработчики, пока не достигнет подходящего.
Передача ошибок через обратные вызовы
В то время как простые и линейные функции упрощают обработку ошибок, обратные вызовы могут усложнить дело.
Рассмотрим следующий фрагмент кода:
Вам нужно решение для хостинга, которое даст вам конкурентное преимущество? Kinsta обеспечит вас невероятной скоростью, ультрасовременной безопасностью и автоматическим масштабированием. Ознакомьтесь с нашими планами
const calculateCube = (number, callback) => { setTimeout(() => { const cube = number * number * number callback(cube) }, 1000) } const callback = result => console.log(result) calculateCube(4, callback)
Вышеупомянутая функция демонстрирует асинхронное состояние, в котором функции требуется некоторое время для обработки операций и позже возвращает результат с помощью обратного вызова.
Если вы попытаетесь ввести строку вместо 4 в вызове функции, то в результате получите NaN
.
С этим нужно правильно обращаться. Вот как:
const calculateCube = (number, callback) => { setTimeout(() => { if (typeof number !== "number") throw new Error("Numeric argument is expected") const cube = number * number * number callback(cube) }, 1000) } const callback = result => console.log(result) try { calculateCube(4, callback) } catch (e) { console.log(e) }
Это должно решить проблему в идеале. Однако, если вы попытаетесь передать строку вызову функции, вы получите следующее:

Несмотря на то, что вы реализовали блок try-catch при вызове функции, он все равно говорит, что ошибка не перехвачена. Ошибка возникает после того, как блок catch был выполнен из-за задержки тайм-аута.
Это может быстро произойти в сетевых вызовах, когда возникают непредвиденные задержки. Такие случаи необходимо предусмотреть при разработке приложения.
Вот как вы можете правильно обрабатывать ошибки в обратных вызовах:
const calculateCube = (number, callback) => { setTimeout(() => { if (typeof number !== "number") { callback(new TypeError("Numeric argument is expected")) return } const cube = number * number * number callback(null, cube) }, 2000) } const callback = (error, result) => { if (error !== null) { console.log(error) return } console.log(result) } try { calculateCube('hey', callback) } catch (e) { console.log(e) }
Теперь вывод в консоли будет таким:

Это указывает на то, что ошибка была правильно обработана.
Обработка ошибок в промисах
Большинство людей предпочитают промисы для обработки асинхронных действий. Промисы имеют еще одно преимущество — отклоненный промис не завершает работу вашего скрипта. Однако вам все равно нужно реализовать блок catch для обработки ошибок в промисах. Чтобы лучше понять это, давайте перепишем функцию calculateCube()
, используя промисы:
const delay = ms => new Promise(res => setTimeout(res, ms)); const calculateCube = async (number) => { if (typeof number !== "number") throw Error("Numeric argument is expected") await delay(5000) const cube = number * number * number return cube } try { calculateCube(4).then(r => console.log(r)) } catch (e) { console.log(e) }
Тайм-аут из предыдущего кода был выделен в функцию delay
для понимания. Если вы попытаетесь ввести строку вместо 4, результат, который вы получите, будет похож на этот:

Опять же, это происходит из-за того, что Promise
выдает ошибку после того, как все остальное завершило выполнение. Решение этой проблемы простое. Просто добавьте вызов catch()
в цепочку промисов следующим образом:
calculateCube("hey") .then(r => console.log(r)) .catch(e => console.log(e))
Теперь вывод будет:

Вы можете наблюдать, как легко обрабатывать ошибки с помощью промисов. Кроме того, вы можете связать блок finally()
и вызов promise, чтобы добавить код, который будет выполняться после завершения обработки ошибок.
Кроме того, вы также можете обрабатывать ошибки в промисах, используя традиционную технику try-catch-finally. Вот как будет выглядеть ваш вызов обещания в этом случае:
try { let result = await calculateCube("hey") console.log(result) } catch (e) { console.log(e) } finally { console.log('Finally executed") }
Однако это работает только внутри асинхронной функции. Поэтому наиболее предпочтительным способом обработки ошибок в промисах является цепочка catch
и, finally
, вызов промиса.
throw/catch, onerror(), обратные вызовы, обещания: что лучше?
Имея в своем распоряжении четыре метода, вы должны знать, как выбрать наиболее подходящий для каждого случая использования. Вот как вы можете решить для себя:
бросить / поймать
Вы будете использовать этот метод большую часть времени. Обязательно реализуйте условия для всех возможных ошибок внутри вашего блока catch и не забудьте включить блок finally, если вам нужно запустить некоторые процедуры очистки памяти после блока try.
Однако слишком много блоков try/catch может затруднить сопровождение вашего кода. Если вы окажетесь в такой ситуации, вы можете захотеть обрабатывать ошибки с помощью глобального обработчика или метода обещания.
При выборе между асинхронными блоками try/catch и обещанием catch()
рекомендуется использовать асинхронные блоки try/catch, поскольку они сделают ваш код линейным и легким для отладки.
при ошибке()
Лучше всего использовать метод onerror()
, когда вы знаете, что ваше приложение должно обрабатывать много ошибок, и они могут быть хорошо разбросаны по кодовой базе. Метод onerror
позволяет вам обрабатывать ошибки, как если бы они были просто еще одним событием, обрабатываемым вашим приложением. Вы можете определить несколько обработчиков ошибок и прикрепить их к окну вашего приложения при первоначальном рендеринге.
Однако вы также должны помнить, что метод onerror()
может быть излишне сложным для настройки в небольших проектах с меньшим количеством ошибок. Если вы уверены, что ваше приложение не будет выдавать слишком много ошибок, вам лучше всего подойдет традиционный метод throw/catch.
Обратные вызовы и обещания
Обработка ошибок в обратных вызовах и промисах различается из-за дизайна и структуры их кода. Однако, если вы выбираете между этими двумя до того, как напишете свой код, лучше всего использовать промисы.
Это связано с тем, что промисы имеют встроенную конструкцию для объединения блоков catch()
и finally()
для легкой обработки ошибок. Этот метод проще и чище, чем определение дополнительных аргументов/повторное использование существующих аргументов для обработки ошибок.
Keep Track of Changes With Git Repositories
Many errors often arise due to manual mistakes in the codebase. While developing or debugging your code, you might end up making unnecessary changes that may cause new errors to appear in your codebase. Automated testing is a great way to keep your code in check after every change. However, it can only tell you if something's wrong. If you don't take frequent backups of your code, you'll end up wasting time trying to fix a function or a script that was working just fine before.
This is where git plays its role. With a proper commit strategy, you can use your git history as a backup system to view your code as it evolved through the development. You can easily browse through your older commits and find out the version of the function working fine before but throwing errors after an unrelated change.
You can then restore the old code or compare the two versions to determine what went wrong. Modern web development tools like GitHub Desktop or GitKraken help you to visualize these changes side by side and figure out the mistakes quickly.
A habit that can help you make fewer errors is running code reviews whenever you make a significant change to your code. If you're working in a team, you can create a pull request and have a team member review it thoroughly. This will help you use a second pair of eyes to spot out any errors that might have slipped by you.
Best Practices for Handling Errors in JavaScript
The above-mentioned methods are adequate to help you design a robust error handling approach for your next JavaScript application. However, it would be best to keep a few things in mind while implementing them to get the best out of your error-proofing. Here are some tips to help you.
1. Use Custom Errors When Handling Operational Exceptions
We introduced custom errors early in this guide to give you an idea of how to customize the error handling to your application's unique case. It's advisable to use custom errors wherever possible instead of the generic Error
class as it provides more contextual information to the calling environment about the error.
On top of that, custom errors allow you to moderate how an error is displayed to the calling environment. This means that you can choose to hide specific details or display additional information about the error as and when you wish.
You can go so far as to format the error contents according to your needs. This gives you better control over how the error is interpreted and handled.
2. Do Not Swallow Any Exceptions
Even the most senior developers often make a rookie mistake — consuming exceptions levels deep down in their code.
You might come across situations where you have a piece of code that is optional to run. If it works, great; if it doesn't, you don't need to do anything about it.
In these cases, it's often tempting to put this code in a try block and attach an empty catch block to it. However, by doing this, you'll leave that piece of code open to causing any kind of error and getting away with it. This can become dangerous if you have a large codebase and many instances of such poor error management constructs.
The best way to handle exceptions is to determine a level on which all of them will be dealt and raise them until there. This level can be a controller (in an MVC architecture app) or a middleware (in a traditional server-oriented app).
This way, you'll get to know where you can find all the errors occurring in your app and choose how to resolve them, even if it means not doing anything about them.
3. Use a Centralized Strategy for Logs and Error Alerts
Logging an error is often an integral part of handling it. Those who fail to develop a centralized strategy for logging errors may miss out on valuable information about their app's usage.
An app's event logs can help you figure out crucial data about errors and help to debug them quickly. If you have proper alerting mechanisms set up in your app, you can know when an error occurs in your app before it reaches a large section of your user base.
It's advisable to use a pre-built logger or create one to suit your needs. You can configure this logger to handle errors based on their levels (warning, debug, info, etc.), and some loggers even go so far as to send logs to remote logging servers immediately. This way, you can watch how your application's logic performs with active users.
4. Notify Users About Errors Appropriately
Another good point to keep in mind while defining your error handling strategy is to keep the user in mind.
All errors that interfere with the normal functioning of your app must present a visible alert to the user to notify them that something went wrong so the user can try to work out a solution. If you know a quick fix for the error, such as retrying an operation or logging out and logging back in, make sure to mention it in the alert to help fix the user experience in real-time.
In the case of errors that don't cause any interference with the everyday user experience, you can consider suppressing the alert and logging the error to a remote server for resolving later.
5. Implement a Middleware (Node.js)
The Node.js environment supports middlewares to add functionalities to server applications. You can use this feature to create an error-handling middleware for your server.
The most significant benefit of using middleware is that all of your errors are handled centrally in one place. You can choose to enable/disable this setup for testing purposes easily.
Here's how you can create a basic middleware:
const logError = err => { console.log("ERROR: " + String(err)) } const errorLoggerMiddleware = (err, req, res, next) => { logError(err) next(err) } const returnErrorMiddleware = (err, req, res, next) => { res.status(err.statusCode || 500) .send(err.message) } module.exports = { logError, errorLoggerMiddleware, returnErrorMiddleware }
Затем вы можете использовать это промежуточное ПО в своем приложении следующим образом:
const { errorLoggerMiddleware, returnErrorMiddleware } = require('./errorMiddleware') app.use(errorLoggerMiddleware) app.use(returnErrorMiddleware)
Теперь вы можете определить пользовательскую логику внутри промежуточного программного обеспечения для правильной обработки ошибок. Вам больше не нужно беспокоиться о внедрении отдельных конструкций обработки ошибок в кодовую базу.
6. Перезапустите приложение для обработки ошибок программиста (Node.js)
Когда приложения Node.js сталкиваются с ошибками программиста, они не обязательно выдают исключение и пытаются закрыть приложение. Такие ошибки могут включать в себя проблемы, возникающие из-за ошибок программиста, такие как высокая загрузка ЦП, раздувание памяти или утечки памяти. Лучший способ справиться с этим — аккуратно перезапустить приложение, завершив его сбоем в режиме кластера Node.js или с помощью уникального инструмента, такого как PM2. Это может гарантировать, что приложение не вылетит из-за действий пользователя, представляя ужасный пользовательский интерфейс.
7. Перехват всех необработанных исключений (Node.js)
Вы никогда не можете быть уверены, что рассмотрели все возможные ошибки, которые могут возникнуть в вашем приложении. Поэтому важно реализовать резервную стратегию, чтобы перехватывать все неперехваченные исключения из вашего приложения.
Вот как это сделать:
process.on('uncaughtException', error => { console.log("ERROR: " + String(error)) // other handling mechanisms })
Вы также можете определить, является ли возникшая ошибка стандартным исключением или пользовательской операционной ошибкой. Основываясь на результате, вы можете выйти из процесса и перезапустить его, чтобы избежать неожиданного поведения.
8. Перехватывайте все необработанные отказы от обещаний (Node.js)
Подобно тому, как вы никогда не сможете покрыть все возможные исключения, существует высокая вероятность того, что вы можете пропустить обработку всех возможных отклонений обещаний. Однако, в отличие от исключений, отказы от обещаний не вызывают ошибок.
Таким образом, важное обещание, которое было отклонено, может проскользнуть как предупреждение и оставить ваше приложение открытым для возможности столкнуться с непредвиденным поведением. Поэтому крайне важно реализовать резервный механизм для обработки отклонения обещания.
Вот как это сделать:
const promiseRejectionCallback = error => { console.log("PROMISE REJECTED: " + String(error)) } process.on('unhandledRejection', callback)
твитнуть Резюме
Как и в любом другом языке программирования, в JavaScript ошибки довольно часты и естественны. В некоторых случаях вам может даже потребоваться намеренно выдавать ошибки, чтобы указать правильный ответ вашим пользователям. Следовательно, понимание их анатомии и типов очень важно.
Кроме того, вы должны быть оснащены правильными инструментами и методами для выявления и предотвращения ошибок, которые могут привести к закрытию вашего приложения.
В большинстве случаев надежной стратегии обработки ошибок с тщательным выполнением достаточно для всех типов приложений JavaScript.
Есть ли какие-либо другие ошибки JavaScript, которые вы до сих пор не смогли исправить? Любые методы конструктивной обработки ошибок JS? Дайте нам знать в комментариях ниже!