Обработка ошибок в JavaScript с try…catch
Обработка ошибок в JavaScript: try, catch, finally, throw, объект Error, встроенные типы ошибок, повторный выброс и асинхронные ограничения.
Обработка ошибок в JavaScript с помощью Try...Catch
Эффективная обработка ошибок — важнейший аспект создания надёжных приложений на JavaScript. Когда во время выполнения что-то идёт не так — сетевой запрос завершается с ошибкой, JSON оказывается некорректным, переменная равна undefined — JavaScript выбрасывает исключение. Без обработки это исключение останавливает выполнение скрипта. В этой статье рассматриваются инструкция try...catch, блок finally, оператор throw, объект Error и его встроенные подтипы, повторный выброс исключений, а также важное ограничение: синхронный try...catch не может поймать ошибки, выброшенные внутри асинхронных колбэков.
Понимание Try...Catch в JavaScript
Инструкция try...catch — инструмент управления исключениями, то есть ошибками, возникающими во время выполнения программы. Она позволяет обрабатывать исключения корректно, не допуская аварийного завершения всего скрипта.
Механизм состоит из двух блоков:
try— код, который может выбросить исключение. JavaScript выполняет его в обычном режиме.catch (error)— выполняется только в том случае, если что-то в блокеtryвыбросило ошибку. Выброшенное значение передаётся в виде параметраerror.
Если ошибки не возникает, блок catch полностью пропускается. Если ошибка возникает, выполнение немедленно переходит в catch — остаток блока try не выполняется.
Базовый синтаксис Try...Catch
Вот простой пример, демонстрирующий базовую структуру try...catch:
В этом примере любая ошибка, возникшая внутри блока try, перехватывается блоком catch, где она может быть обработана без аварийного завершения скрипта.
Выброс собственных ошибок с помощью throw
Оператор throw генерирует исключение. Можно выбросить любое значение, однако принятое соглашение — и единственное, что следует делать на практике — это выбрасывать объект Error (или экземпляр одного из его подтипов). Это автоматически предоставляет полезные свойства message и трассировку stack.
Избегайте throw 'a string' или throw 42. У выброшенной строки нет свойств message, name и stack, поэтому код, ожидающий объект Error (как большинство кода), будет работать некорректно. Для предметно-ориентированных ошибок с дополнительными полями определите собственный класс — см. пользовательские ошибки, расширение Error.
Объект Error
При выполнении new Error(message) вы получаете объект с тремя часто используемыми свойствами:
name— тип ошибки, например"Error","TypeError","SyntaxError".message— понятное человеку описание, переданное в конструктор.stack— строка со стеком вызовов в момент создания ошибки (нестандартное, но поддерживаемое всеми основными движками свойство; полезно для отладки, но не для управления потоком выполнения).
Встроенные типы ошибок
JavaScript поставляется с несколькими подклассами Error, которые движок выбрасывает автоматически. Знание этих типов помогает писать точную логику обработки в catch:
| Тип | Когда выбрасывается |
|---|---|
SyntaxError | Код или данные имеют неверный формат, например JSON.parse() при некорректном JSON. |
TypeError | Значение имеет неожиданный тип, например вызов не-функции или чтение свойства у undefined. |
ReferenceError | Обращение к несуществующей переменной. |
RangeError | Значение выходит за допустимый диапазон, например недопустимая длина array. |
URIError | decodeURIComponent() или аналогичная функция получила некорректный URI. |
EvalError | Историческое исключение; редко выбрасывается современными движками. |
У каждого из этих типов свойство name соответствует имени типа, поэтому можно делать ветвление по instanceof:
Обработка конкретных ошибок
Можно также обрабатывать определённые типы ошибок, анализируя объект ошибки:
Этот пример специально обрабатывает SyntaxError, которая может возникнуть при разборе JSON. Если перехваченная ошибка является экземпляром SyntaxError, она обрабатывается путём вывода конкретного сообщения. В противном случае ошибка повторно выбрасывается — она может быть перехвачена обработчиком более высокого уровня или привести к аварийному завершению программы, сигнализируя о необработанной ошибке.
Повторный выброс ошибок
Блок catch перехватывает все ошибки из своего try, включая те, которые вы не знаете как обработать. Рекомендуемый паттерн: проверить ошибку, обработать понятные случаи и повторно выбросить всё остальное, чтобы оно попало к внешнему обработчику. Молчаливое поглощение неизвестных ошибок скрывает реальные баги.
Необязательная привязка в catch
Если значение ошибки не нужно, привязку можно опустить полностью (ES2019+). Это удобно, когда важен лишь сам факт сбоя:
Использование Finally
Блок finally выполняется после блоков try и catch независимо от того, было ли выброшено или перехвачено исключение. Он полезен для освобождения ресурсов или выполнения завершающих операций вне зависимости от результата try...catch:
Это гарантирует вывод сообщения «Finally block executed» как при возникновении ошибки, так и без неё, демонстрируя, как finally можно использовать для выполнения необходимых завершающих действий.
Примеры реальных API-запросов
JSONPlaceholder API — отличный инструмент для практики работы с реальными данными в JavaScript, особенно при работе с асинхронными запросами и обработкой возможных ошибок. Ниже приведены несколько практических примеров с использованием JSONPlaceholder API, который предоставляет фиктивные данные REST для тестирования и прототипирования.
Пример 1: Получение записей и обработка ошибок
В этом примере мы получаем записи из JSONPlaceholder API с помощью fetch и обрабатываем возможные сетевые ошибки или проблемы с ответом API:
Этот скрипт выполняет HTTP-запрос для получения одного элемента todo. Он проверяет успешность ответа (то есть статус HTTP 200-299). Если ответ неуспешный, выбрасывается ошибка со статусом ответа. Любые ошибки — как от сетевых проблем, так и от инструкции throw — перехватываются в блоке catch и записываются в журнал. Блок finally выполняется независимо от результата, обеспечивая завершение всех необходимых операций.
Пример 2: Отправка данных и обработка исключений
Здесь показано, как отправлять данные на сервер методом POST и корректно обрабатывать исключения:
В этом скрипте мы отправляем новую запись на сервер. Функция fetch используется с методом POST, включая заголовки и тело в формате JSON. Если ответ сервера указывает на сбой (статус HTTP не из диапазона 2xx), выбрасывается ошибка, которая перехватывается и обрабатывается в блоке catch. Независимо от успеха или неудачи, блок finally гарантирует фиксацию завершения операции.
Пример 3: Намеренный вызов ошибки и её обработка
В этом примере намеренно запрашивается несуществующий на JSONPlaceholder API идентификатор пользователя, что вызывает ошибку 404 Not Found, которую мы перехватим и обработаем.
Как работает этот пример
- Недопустимый API-эндпоинт: функция
fetchвызывается с URL, содержащим недопустимый идентификатор пользователя (99999). Поскольку JSONPlaceholder обычно не имеет пользователя с таким индексом, API вернёт ошибку 404. - Проверка корректности ответа: код проверяет, что статус ответа не входит в успешный диапазон (200-299). Поскольку идентификатор пользователя недопустим, ответ API скорее всего будет 404, что активирует обработку ошибок в проверке
if (!response.ok). - Выброс ошибки: так как ответ неуспешный, выбрасывается ошибка с сообщением, включающим статус HTTP, который в данном случае указывает на ошибку 404 Not Found.
- Блок Catch: блок catch перехватывает выброшенную ошибку и записывает конкретное сообщение с помощью
console.log. Это даёт чёткую обратную связь о том, что пошло не так. - Блок Finally: этот блок используется для очистки или финальных операций и сигнализирует о завершении попытки независимо от её результата.
Почему Try...Catch не может поймать ошибки асинхронных колбэков
Это самая распространённая ловушка при работе с try...catch. Данная инструкция является синхронной: она перехватывает только ошибки, выброшенные во время текущего выполнения блока try. Когда вы планируете колбэк через setTimeout, обработчик событий или ожидаемый промис, колбэк выполняется позже — после того, как try...catch уже завершился и стек вызовов пуст. К тому моменту нет никакого окружающего try, который мог бы что-то перехватить.
Чтобы обработать ошибку, try...catch должен находиться внутри колбэка, там, где ошибка фактически выбрасывается:
Это же правило объясняет, почему try...catch работает с async/await, но не с голыми промисами. await приостанавливает функцию и возобновляет её в том же логическом потоке, поэтому отклонённый промис проявляется как выброшенная ошибка, которую может поймать try. Промис, который не является await-ожидаемым, выполняется независимо и обходит окружающий try — вместо этого нужно использовать .catch().
Для более детального изучения асинхронной обработки ошибок см. Обработка ошибок с промисами и async/await.
Заключение
Эффективная обработка ошибок в JavaScript является ключевым аспектом разработки высококачественных и устойчивых приложений. Использование try...catch позволяет корректно обрабатывать синхронные ошибки и отклонения await-промисов, сохраняя контроль над потоком выполнения приложения. Выбрасывайте объекты Error (никогда не голые строки), делайте ветвление по встроенным типам, таким как TypeError и SyntaxError, повторно выбрасывайте то, что не умеете обрабатывать, и помните об асинхронном ограничении: ошибки в колбэках и неожидаемых промисах требуют собственной обработки.
Связанные темы
- Пользовательские ошибки, расширение Error — определение собственных классов ошибок.
- Обработка ошибок с промисами —
.catch()и поток отклонений. - Async/await —
try...catchс асинхронным кодом. - Работа с JSON —
JSON.parseиSyntaxError, которую он может выбросить.