Javascript try catch
Перехват ошибок, «try..catch»
Материал на этой странице устарел, поэтому скрыт из оглавления сайта.
Более новая информация по этой теме находится на странице https://learn.javascript.ru/try-catch.
Как бы мы хорошо ни программировали, в коде бывают ошибки. Или, как их иначе называют, «исключительные ситуации» (исключения).
Обычно скрипт при ошибке, как говорят, «падает», с выводом ошибки в консоль.
Но бывают случаи, когда нам хотелось бы как-то контролировать ситуацию, чтобы скрипт не просто «упал», а сделал что-то разумное.
Для этого в JavaScript есть замечательная конструкция try..catch .
Конструкция try…catch
Конструкция try..catch состоит из двух основных блоков: try , и затем catch :
Работает она так:
Выполняется код внутри блока try .
Если в нём ошибок нет, то блок catch(err) игнорируется, то есть выполнение доходит до конца try и потом прыгает через catch .
Если в нём возникнет ошибка, то выполнение try на ней прерывается, и управление прыгает в начало блока catch(err) .
При этом переменная err (можно выбрать и другое название) будет содержать объект ошибки с подробной информацией о произошедшем.
Таким образом, при ошибке в try скрипт не «падает», и мы получаем возможность обработать ошибку внутри catch .
Посмотрим это на примерах.
Пример без ошибок: при запуске сработают alert (1) и (2) :
Пример с ошибкой: при запуске сработают (1) и (3) :
Если грубо нарушена структура кода, например не закрыта фигурная скобка или где-то стоит лишняя запятая, то никакой try..catch здесь не поможет. Такие ошибки называются синтаксическими, интерпретатор не может понять такой код.
Здесь же мы рассматриваем ошибки семантические, то есть происходящие в корректном коде, в процессе выполнения.
Ошибку, которая произойдёт в коде, запланированном «на будущее», например в setTimeout , try..catch не поймает:
На момент запуска функции, назначенной через setTimeout , этот код уже завершится, интерпретатор выйдет из блока try..catch .
Чтобы поймать ошибку внутри функции из setTimeout , и try..catch должен быть в той же функции.
Объект ошибки
В примере выше мы видим объект ошибки. У него есть три основных свойства:
name Тип ошибки. Например, при обращении к несуществующей переменной: «ReferenceError» . message Текстовое сообщение о деталях ошибки. stack Везде, кроме IE8-, есть также свойство stack , которое содержит строку с информацией о последовательности вызовов, которая привела к ошибке.
В зависимости от браузера у него могут быть и дополнительные свойства, см. Error в MDN и Error в MSDN.
Пример использования
В JavaScript есть встроенный метод JSON.parse(str), который используется для чтения JavaScript-объектов (и не только) из строки.
Обычно он используется для того, чтобы обрабатывать данные, полученные по сети, с сервера или из другого источника.
Мы получаем их и вызываем метод JSON.parse , вот так:
Более детально формат JSON разобран в главе Формат JSON, метод toJSON.
В случае, если данные некорректны, JSON.parse генерирует ошибку, то есть скрипт «упадёт».
Устроит ли нас такое поведение? Конечно нет!
Получается, что если вдруг что-то не так с данными, то посетитель никогда (если, конечно, не откроет консоль) об этом не узнает.
А люди очень-очень не любят, когда что-то «просто падает», без всякого объявления об ошибке.
Бывают ситуации, когда без try..catch не обойтись, это – одна из таких.
Используем try..catch , чтобы обработать некорректный ответ:
Здесь в alert только выводится сообщение, но область применения гораздо шире: можно повторять запрос, можно предлагать посетителю использовать альтернативный способ, можно отсылать информацию об ошибке на сервер… Свобода действий.
Генерация своих ошибок
Представим на минуту, что данные являются корректным JSON… Но в этом объекте нет нужного свойства name :
Вызов JSON.parse выполнится без ошибок, но ошибка в данных есть. И, так как свойство name обязательно должно быть, то для нас это такие же некорректные данные, как и «Has Error» .
Для того, чтобы унифицировать и объединить обработку ошибок парсинга и ошибок в структуре, мы воспользуемся оператором throw .
Оператор throw
Оператор throw генерирует ошибку.
Технически в качестве объекта ошибки можно передать что угодно, это может быть даже не объект, а число или строка, но всё же лучше, чтобы это был объект, желательно – совместимый со стандартным, то есть чтобы у него были как минимум свойства name и message .
В качестве конструктора ошибок можно использовать встроенный конструктор: new Error(message) или любой другой.
В JavaScript встроен ряд конструкторов для стандартных ошибок: SyntaxError , ReferenceError , RangeError и некоторые другие. Можно использовать и их, но только чтобы не было путаницы.
В данном случае мы используем конструктор new SyntaxError(message) . Он создаёт ошибку того же типа, что и JSON.parse .
Получилось, что блок catch – единое место для обработки ошибок во всех случаях: когда ошибка выявляется при JSON.parse или позже.
Проброс исключения
В коде выше мы предусмотрели обработку ошибок, которые возникают при некорректных данных. Но может ли быть так, что возникнет какая-то другая ошибка?
Конечно, может! Код – это вообще мешок с ошибками, бывает даже так, что библиотеку выкладывают в открытый доступ, она там 10 лет лежит, её смотрят миллионы людей и на 11-й год находятся опаснейшие ошибки. Такова жизнь, таковы люди.
Блок catch в нашем примере предназначен для обработки ошибок, возникающих при некорректных данных. Если же в него попала какая-то другая ошибка, то вывод сообщения о «некорректных данных» будет дезинформацией посетителя.
Ошибку, о которой catch не знает, он не должен обрабатывать.
Такая техника называется «проброс исключения»: в catch(e) мы анализируем объект ошибки, и если он нам не подходит, то делаем throw e .
При этом ошибка «выпадает» из try..catch наружу. Далее она может быть поймана либо внешним блоком try..catch (если есть), либо «повалит» скрипт.
В примере ниже catch обрабатывает только ошибки SyntaxError , а остальные – выбрасывает дальше:
Заметим, что ошибка, которая возникла внутри блока catch , «выпадает» наружу, как если бы была в обычном коде.
В следующем примере такие ошибки обрабатываются ещё одним, «более внешним» try..catch :
В примере выше try..catch внутри readData умеет обрабатывать только SyntaxError , а внешний – все ошибки.
Без внешнего проброшенная ошибка «вывалилась» бы в консоль с остановкой скрипта.
Оборачивание исключений
И, для полноты картины – последняя, самая продвинутая техника по работе с ошибками. Она, впрочем, является стандартной практикой во многих объектно-ориентированных языках.
Цель функции readData в примере выше – прочитать данные. При чтении могут возникать разные ошибки, не только SyntaxError , но и, возможно, к примеру URIError (неправильное применение функций работы с URI) да и другие.
Код, который вызвал readData , хотел бы иметь либо результат, либо информацию об ошибке.
При этом очень важным является вопрос: обязан ли этот внешний код знать о всевозможных типах ошибок, которые могут возникать при чтении данных, и уметь перехватывать их?
Обычно внешний код хотел бы работать «на уровень выше», и получать либо результат, либо «ошибку чтения данных», при этом какая именно ошибка произошла – ему неважно. Ну, или, если будет важно, то хотелось бы иметь возможность это узнать, но обычно не требуется.
Это важнейший общий подход к проектированию – каждый участок функциональности должен получать информацию на том уровне, который ей необходим.
Мы его видим везде в грамотно построенном коде, но не всегда отдаём себе в этом отчёт.
В данном случае, если при чтении данных происходит ошибка, то мы будем генерировать её в виде объекта ReadError , с соответствующим сообщением. А «исходную» ошибку на всякий случай тоже сохраним, присвоим в свойство cause (англ. – причина).
Выглядит это так:
Этот подход называют «оборачиванием» исключения, поскольку мы берём ошибки «более низкого уровня» и «заворачиваем» их в ReadError , которая соответствует текущей задаче.
Секция finally
Конструкция try..catch может содержать ещё один блок: finally .
Выглядит этот расширенный синтаксис так:
Секция finally не обязательна, но если она есть, то она выполняется всегда:
- после блока try , если ошибок не было,
- после catch , если они были.
Попробуйте запустить такой код?
У него два варианта работы:
- Если вы ответите на вопрос «сгенерировать ошибку?» утвердительно, то try -> catch -> finally .
- Если ответите отрицательно, то try -> finally .
Секцию finally используют, чтобы завершить начатые операции при любом варианте развития событий.
Например, мы хотим подсчитать время на выполнение функции sum(n) , которая должна возвратить сумму чисел от 1 до n и работает рекурсивно:
Здесь секция finally гарантирует, что время будет подсчитано в любых ситуациях: при ошибке в sum или без неё.
Вы можете проверить это, запустив код с указанием n=100 – будет без ошибки, finally выполнится после try , а затем с n=100000 – будет ошибка из-за слишком глубокой рекурсии, управление прыгнет в finally после catch .
Блок finally срабатывает при любом выходе из try..catch , в том числе и return .
В примере ниже из try происходит return , но finally получает управление до того, как контроль возвращается во внешний код.
Если внутри try были начаты какие-то процессы, которые нужно завершить по окончании работы, то в finally это обязательно будет сделано.
Кстати, для таких случаев иногда используют try..finally вообще без catch :
В примере выше try..finally вообще не обрабатывает ошибки. Задача в другом: выполнить код при любом выходе из try – с ошибкой ли, без ошибок или через return .
Последняя надежда: window.onerror
Допустим, ошибка произошла вне блока try..catch или выпала из try..catch наружу, во внешний код. Скрипт упал.
Можно ли как-то узнать о том, что произошло? Да, конечно.
В браузере существует специальное свойство window.onerror , если в него записать функцию, то она выполнится и получит в аргументах сообщение ошибки, текущий URL и номер строки, откуда «выпала» ошибка.
Необходимо лишь позаботиться, чтобы функция была назначена заранее.
Как правило, роль window.onerror заключается не в том, чтобы оживить скрипт – скорее всего, это уже невозможно, а в том, чтобы отослать сообщение об ошибке на сервер, где разработчики о ней узнают.
Существуют даже специальные веб-сервисы, которые предоставляют скрипты для отлова и аналитики таких ошибок, например: https://errorception.com/ или http://www.muscula.com/.
Итого
Обработка ошибок – большая и важная тема.
В JavaScript для этого предусмотрены:
Конструкция try..catch..finally – она позволяет обработать произвольные ошибки в блоке кода.
Это удобно в тех случаях, когда проще сделать действие и потом разбираться с результатом, чем долго и нудно проверять, не упадёт ли чего.
Кроме того, иногда проверить просто невозможно, например JSON.parse(str) не позволяет «проверить» формат строки перед разбором. В этом случае блок try..catch необходим.
Полный вид конструкции:
Возможны также варианты try..catch или try..finally .
Оператор throw err генерирует свою ошибку, в качестве err рекомендуется использовать объекты, совместимые с встроенным типом Error, содержащие свойства message и name .
Кроме того, мы рассмотрели некоторые важные приёмы:
Проброс исключения – catch(err) должен обрабатывать только те ошибки, которые мы рассчитываем в нём увидеть, остальные – пробрасывать дальше через throw err .
Определить, нужная ли это ошибка, можно, например, по свойству name .
Оборачивание исключений – функция, в процессе работы которой возможны различные виды ошибок, может «обернуть их» в одну общую ошибку, специфичную для её задачи, и уже её пробросить дальше. Чтобы при необходимости можно было подробно определить, что произошло, исходную ошибку обычно присваивают в свойство этой, общей. Обычно это нужно для логирования.
В window.onerror можно присвоить функцию, которая выполнится при любой «выпавшей» из скрипта ошибке. Как правило, это используют в информационных целях, например отправляют информацию об ошибке на специальный сервис.
Задачи
Eval-калькулятор с ошибками
Напишите интерфейс, который принимает математическое выражение (в prompt ) и выводит результат его вычисления через eval .
При ошибке нужно выводить сообщение и просить переввести выражение.
Ошибкой считается не только некорректное выражение, такое как 2+ , но и выражение, возвращающее NaN , например 0/0 .
Вычислить любое выражение нам поможет eval :
Считываем выражение в цикле while(true) . Если при вычислении возникает ошибка – ловим её в try..catch .
Ошибкой считается, в том числе, получение NaN из eval , хотя при этом исключение не возникает. Можно бросить своё исключение в этом случае.
Обработка ошибок, «try..catch»
Неважно, насколько мы хороши в программировании, иногда наши скрипты содержат ошибки. Они могут возникать из-за наших промахов, неожиданного ввода пользователя, неправильного ответа сервера и по тысяче других причин.
Обычно скрипт в случае ошибки «падает» (сразу же останавливается), с выводом ошибки в консоль.
Но есть синтаксическая конструкция try..catch , которая позволяет «ловить» ошибки и вместо падения делать что-то более осмысленное.
Синтаксис «try…catch»
Конструкция try..catch состоит из двух основных блоков: try , и затем catch :
Работает она так:
- Сначала выполняется код внутри блока try <. >.
- Если в нём нет ошибок, то блок catch(err) игнорируется: выполнение доходит до конца try и потом далее, полностью пропуская catch .
- Если же в нём возникает ошибка, то выполнение try прерывается, и поток управления переходит в начало catch(err) . Переменная err (можно использовать любое имя) содержит объект ошибки с подробной информацией о произошедшем.
Таким образом, при ошибке в блоке try <…>скрипт не «падает», и мы получаем возможность обработать ошибку внутри catch .
Давайте рассмотрим примеры.
Пример без ошибок: выведет alert (1) и (2) :
Пример с ошибками: выведет (1) и (3) :
Чтобы try..catch работал, код должен быть выполнимым. Другими словами, это должен быть корректный JavaScript-код.
Он не сработает, если код синтаксически неверен, например, содержит несовпадающее количество фигурных скобок:
JavaScript-движок сначала читает код, а затем исполняет его. Ошибки, которые возникают во время фазы чтения, называются ошибками парсинга. Их нельзя обработать (изнутри этого кода), потому что движок не понимает код.
Таким образом, try..catch может обрабатывать только ошибки, которые возникают в корректном коде. Такие ошибки называют «ошибками во время выполнения», а иногда «исключениями».
Исключение, которое произойдёт в коде, запланированном «на будущее», например в setTimeout , try..catch не поймает:
Это потому, что функция выполняется позже, когда движок уже покинул конструкцию try..catch .
Чтобы поймать исключение внутри запланированной функции, try..catch должен находиться внутри самой этой функции:
Объект ошибки
Когда возникает ошибка, JavaScript генерирует объект, содержащий её детали. Затем этот объект передаётся как аргумент в блок catch :
Для всех встроенных ошибок этот объект имеет два основных свойства:
name Имя ошибки. Например, для неопределённой переменной это «ReferenceError» . message Текстовое сообщение о деталях ошибки.
В большинстве окружений доступны и другие, нестандартные свойства. Одно из самых широко используемых и поддерживаемых – это:
stack Текущий стек вызова: строка, содержащая информацию о последовательности вложенных вызовов, которые привели к ошибке. Используется в целях отладки.
Блок «catch» без переменной
Если нам не нужны детали ошибки, в catch можно её пропустить:
Использование «try…catch»
Давайте рассмотрим реальные случаи использования try..catch .
Как мы уже знаем, JavaScript поддерживает метод JSON.parse(str) для чтения JSON.
Обычно он используется для декодирования данных, полученных по сети, от сервера или из другого источника.
Мы получаем их и вызываем JSON.parse вот так:
Вы можете найти более детальную информацию о JSON в главе Формат JSON, метод toJSON.
Если json некорректен, JSON.parse генерирует ошибку, то есть скрипт «падает».
Устроит ли нас такое поведение? Конечно нет!
Получается, что если вдруг что-то не так с данными, то посетитель никогда (если, конечно, не откроет консоль) об этом не узнает. А люди очень не любят, когда что-то «просто падает» без всякого сообщения об ошибке.
Давайте используем try..catch для обработки ошибки:
Здесь мы используем блок catch только для вывода сообщения, но мы также можем сделать гораздо больше: отправить новый сетевой запрос, предложить посетителю альтернативный способ, отослать информацию об ошибке на сервер для логирования, … Всё лучше, чем просто «падение».
Генерация собственных ошибок
Что если json синтаксически корректен, но не содержит необходимого свойства name ?
Здесь JSON.parse выполнится без ошибок, но на самом деле отсутствие свойства name для нас ошибка.
Для того, чтобы унифицировать обработку ошибок, мы воспользуемся оператором throw .
Оператор «throw»
Оператор throw генерирует ошибку.
Технически в качестве объекта ошибки можно передать что угодно. Это может быть даже примитив, число или строка, но всё же лучше, чтобы это был объект, желательно со свойствами name и message (для совместимости со встроенными ошибками).
В JavaScript есть множество встроенных конструкторов для стандартных ошибок: Error , SyntaxError , ReferenceError , TypeError и другие. Можно использовать и их для создания объектов ошибки.
Для встроенных ошибок (не для любых объектов, только для ошибок), свойство name – это в точности имя конструктора. А свойство message берётся из аргумента.
Давайте посмотрим, какую ошибку генерирует JSON.parse :
Как мы видим, это SyntaxError .
В нашем случае отсутствие свойства name – это ошибка, ведь пользователи должны иметь имена.
В строке (*) оператор throw генерирует ошибку SyntaxError с сообщением message . Точно такого же вида, как генерирует сам JavaScript. Выполнение блока try немедленно останавливается, и поток управления прыгает в catch .
Теперь блок catch становится единственным местом для обработки всех ошибок: и для JSON.parse и для других случаев.
Проброс исключения
В примере выше мы использовали try..catch для обработки некорректных данных. А что, если в блоке try <. >возникнет другая неожиданная ошибка? Например, программная (неопределённая переменная) или какая-то ещё, а не ошибка, связанная с некорректными данными.
Конечно, возможно все! Программисты совершают ошибки. Даже в утилитах с открытым исходным кодом, используемых миллионами людей на протяжении десятилетий – вдруг может быть обнаружена ошибка, которая приводит к ужасным взломам.
В нашем случае try..catch предназначен для выявления ошибок, связанных с некорректными данными. Но по своей природе catch получает все свои ошибки из try . Здесь он получает неожиданную ошибку, но всё также показывает то же самое сообщение «JSON Error» . Это неправильно и затрудняет отладку кода.
К счастью, мы можем выяснить, какую ошибку мы получили, например, по её свойству name :
Есть простое правило:
Блок catch должен обрабатывать только те ошибки, которые ему известны, и «пробрасывать» все остальные.
Техника «проброс исключения» выглядит так:
- Блок catch получает все ошибки.
- В блоке catch(err) <. >мы анализируем объект ошибки err .
- Если мы не знаем как её обработать, тогда делаем throw err .
В коде ниже мы используем проброс исключения, catch обрабатывает только SyntaxError :
Ошибка в строке (*) из блока catch «выпадает наружу» и может быть поймана другой внешней конструкцией try..catch (если есть), или «убьёт» скрипт.
Таким образом, блок catch фактически обрабатывает только те ошибки, с которыми он знает, как справляться, и пропускает остальные.
Пример ниже демонстрирует, как такие ошибки могут быть пойманы с помощью ещё одного уровня try..catch :
Здесь readData знает только, как обработать SyntaxError , тогда как внешний блок try..catch знает, как обработать всё.
try…catch…finally
Подождите, это ещё не всё.
Конструкция try..catch может содержать ещё одну секцию: finally .
Если секция есть, то она выполняется в любом случае:
- после try , если не было ошибок,
- после catch , если ошибки были.
Расширенный синтаксис выглядит следующим образом:
Попробуйте запустить такой код:
У кода есть два пути выполнения:
- Если вы ответите на вопрос «Сгенерировать ошибку?» утвердительно, то try -> catch -> finally .
- Если ответите отрицательно, то try -> finally .
Секцию finally часто используют, когда мы начали что-то делать и хотим завершить это вне зависимости от того, будет ошибка или нет.
Например, мы хотим измерить время, которое занимает функция чисел Фибоначчи fib(n) . Естественно, мы можем начать измерения до того, как функция начнёт выполняться и закончить после. Но что делать, если при вызове функции возникла ошибка? В частности, реализация fib(n) в коде ниже возвращает ошибку для отрицательных и для нецелых чисел.
Секция finally отлично подходит для завершения измерений несмотря ни на что.
Здесь finally гарантирует, что время будет измерено корректно в обеих ситуациях – и в случае успешного завершения fib и в случае ошибки:
Вы можете это проверить, запустив этот код и введя 35 в prompt – код завершится нормально, finally выполнится после try . А затем введите -1 – незамедлительно произойдёт ошибка, выполнение займёт 0ms . Оба измерения выполняются корректно.
Другими словами, неважно как завершилась функция: через return или throw . Секция finally срабатывает в обоих случаях.
Обратите внимание, что переменные result и diff в коде выше объявлены до try..catch .
Если переменную объявить в блоке, например, в try , то она не будет доступна после него.
Блок finally срабатывает при любом выходе из try..catch , в том числе и return .
В примере ниже из try происходит return , но finally получает управление до того, как контроль возвращается во внешний код.
Конструкция try..finally без секции catch также полезна. Мы применяем её, когда не хотим здесь обрабатывать ошибки (пусть выпадут), но хотим быть уверены, что начатые процессы завершились.
В приведённом выше коде ошибка всегда выпадает наружу, потому что тут нет блока catch . Но finally отрабатывает до того, как поток управления выйдет из функции.
Глобальный catch
Информация из данной секции не является частью языка JavaScript.
Давайте представим, что произошла фатальная ошибка (программная или что-то ещё ужасное) снаружи try..catch , и скрипт упал.
Существует ли способ отреагировать на такие ситуации? Мы можем захотеть залогировать ошибку, показать что-то пользователю (обычно они не видят сообщение об ошибке) и т.д.
Такого способа нет в спецификации, но обычно окружения предоставляют его, потому что это весьма полезно. Например, в Node.js для этого есть process.on(«uncaughtException») . А в браузере мы можем присвоить функцию специальному свойству window.onerror, которая будет вызвана в случае необработанной ошибки.
Роль глобального обработчика window.onerror обычно заключается не в восстановлении выполнения скрипта – это скорее всего невозможно в случае программной ошибки, а в отправке сообщение об ошибке разработчикам.
Существуют также веб-сервисы, которые предоставляют логирование ошибок для таких случаев, такие как https://errorception.com или http://www.muscula.com.
Они работают так:
- Мы регистрируемся в сервисе и получаем небольшой JS-скрипт (или URL скрипта) от них для вставки на страницы.
- Этот JS-скрипт ставит свою функцию window.onerror .
- Когда возникает ошибка, она выполняется и отправляет сетевой запрос с информацией о ней в сервис.
- Мы можем войти в веб-интерфейс сервиса и увидеть ошибки.
Итого
Конструкция try..catch позволяет обрабатывать ошибки во время исполнения кода. Она позволяет запустить код и перехватить ошибки, которые могут в нём возникнуть.
Секций catch или finally может не быть, то есть более короткие конструкции try..catch и try..finally также корректны.
Объекты ошибок содержат следующие свойства:
- message – понятное человеку сообщение.
- name – строка с именем ошибки (имя конструктора ошибки).
- stack (нестандартное, но хорошо поддерживается) – стек на момент ошибки.
Если объект ошибки не нужен, мы можем пропустить его, используя catch < вместо catch(err) < .
Мы можем также генерировать собственные ошибки, используя оператор throw . Аргументом throw может быть что угодно, но обычно это объект ошибки, наследуемый от встроенного класса Error . Подробнее о расширении ошибок см. в следующей главе.
Проброс исключения – это очень важный приём обработки ошибок: блок catch обычно ожидает и знает, как обработать определённый тип ошибок, поэтому он должен пробрасывать дальше ошибки, о которых он не знает.
- Главная   /  Уроки   /  Уроки JavaScript   /  Глава 2. Основы JavaScript   /  
- Перехват ошибок в JavaScript, «try..catch»
Перехват ошибок в JavaScript, «try..catch»
Здравствуйте! В этом уроке я хотел бы рассказать об ошибках в JavaScript и о том как их собственно обрабатывать. Ведь часто случается, что ошибки происходят время от времени и тут даже дело не в наличие опыта программирования или даже полном его отсутствии. Ведь и у матерых программистов тоже случаются ошибки никто от этого не застрахован.
Ошибки бывают в основном 2-х типов — это синтаксические и логические. К синтаксическим можно отнести ошибки в имени переменных, функций, ошибки в синтаксисе кода. В принципе такие ошибки легко отловить через консоль браузера.
А вот логические ошибки с ними все не так просто потому, что они приводят к неправильному выполнению кода программы. Поэтому для их устранения потребуется отладка программы, чтобы понять что собственно происходит на каждом шаге скрипта. Мы же с вами здесь рассмотрим в основном локализацию синтаксических ошибок с помощью конструкции try…catch.
Конструкция перехвата ошибок try…catch
Конструкция try..catch состит из 2-х блоков: try, и затем catch. Вот пример записи в общем виде
Работает эта конструкция таким образом:
- Выполняется код внутри блока try, так называемой ловушки.
- Если в нём не встречаются ошибки, то блок catch(err) игнорируется.
- А вот, если в нём возникнет ошибка, то выполнение try будет прервано на ошибке, и управление передается в начало блока catch(err). При этом переменная err (можно выбрать любое другое название) будет содержать объект ошибки с подробнейшей информацией о произошедшей ошибке.
Поэтому при ошибке в try скрипт не останавливается, и даже более того мы имеем возможность обработать ошибку внутри блока catch.
Рассмотрим это на примерах.
- Пример без ошибок: при запуске сработают alert (1) и (2):
А вот пример с ошибкой: при запуске сработают (1) и (3):
В случае, если грубо нарушена структура кода, не закрыта фигурная скобка или где-то стоит лишняя запятая, то вам никакой try..catch не поможет. Это синтаксические ошибки, интерпретатор такой код просто не понимает.
Важное замечание try..catch может работать только в синхронном коде
Ошибку, которая может произойти в коде,который будет выполняться через время например в setTimeout, try..catch не поймает:
На момент запуска функции, назначенной через setTimeout, этот код уже завершится, и интерпретатор выйдет из блока try..catch.
Для того чтобы Чтобы поймать ошибку внутри функции из setTimeout, и try..catch должен быть в той же функции.
Объект ошибки
В примере выше мы видим объект ошибки, который вы передаете в блок catch. У него есть три основных свойства:
name Тип ошибки. Например, при обращении к переменной, которой нет: «ReferenceError». message Сообщение о деталях ошибки stack Выводит более полную информацию, с указанием строки, где произошла ошибка и саму ошибку.
Генерация своих ошибок
Оператор throw
Данный оператор throw генерирует ошибку.
Технически в качестве объекта ошибки вы можете передать что угодно, это может быть даже и не объект, а число или строка.
В качестве конструктора ошибок вы можете использовать встроенный конструктор: new Error(message) или любой другой.
В JavaScript также встроен ряд конструкторов для стандартных ошибок: SyntaxError, ReferenceError, RangeError и некоторые другие.
В данном примере мы используем конструктор new SyntaxError(message). Он создаёт ошибку того же типа, что и JSON.parse.
Получается, что блок catch – единственное место для обработки ошибок во всех случаях.
Оборачивание исключений
И, для полноты картины – последняя, техника по работе с ошибками. Она, является стандартной практикой во многих языках программирования.
Цель функции readData в примере выше – это прочитать данные. При этом чтении могут возникать различные ошибки, не только SyntaxError, но и, возможно, к примеру URIError и другие.
Код, который вызвает readData, хотел бы иметь конечно либо результат, либо информацию об ошибке.
При этом очень важным является следующий вопрос: обязан ли этот внешний код знать о типах ошибок, которые возникают при чтении данных, и конечно же уметь перехватывать их?
В данном случае, если при чтении данных происходит ошибка, то будем генерировать её в виде объекта ReadError, с сообщением. А «исходную» ошибку на всякий случай тоже сохраним, присвоим в свойство cause.
Выглядит это так:
Этот подход называют «оборачиванием» исключения, поскольку мы берём ошибки «более низкого уровня» и «заворачиваем» их в ReadError, которая соответствует текущей задаче.
Секция finally
В конструкции try..catch есть и ещё один блок: finally.
Выглядит этот расширенный синтаксис так:
Секция finally не обязательна, но если есть, то она будет выполнена всегда, независимо от того были ошибки или нет:
- после блока try, в случае если ошибок не было,
- после catch, в случае если они были.
Попробуйте запустить такой код?
У него 2 варианта работы:
- Если вы ответите на вопрос «сгенерировать ошибку?» утвердительно, то try -> catch -> finally.
- Если ответите отрицательно, то try -> finally.
Секцию finally используют, чтобы завершить начатые операции при любом варианте развития событий.
Последняя надежда: window.onerror
Допустим, ошибка произошла вне блока try..catch или выпала из try..catch наружу, во внешний код. Скрипт перестал работать.
Можно ли как-то узнать о том, что произошло? Да можно.
В браузере есть специальное свойство window.onerror, если в него записать функцию, то она выполнится и получит в аргументах сообщение ошибки, текущий URL и номер строки, откуда «выпала» ошибка.
Необходимо лишь позаботиться, чтобы функция была назначена заранее.
Как правило, роль window.onerror заключается не в том, чтобы оживить скрипт – скорее всего, это уже невозможно, а в том, чтобы отослать сообщение об ошибке на сервер, где разработчики о ней узнают.
Существуют даже специальные веб-сервисы, которые предоставляют скрипты для отлова и аналитики таких ошибок, например: https://errorception.com/ и http://www.muscula.com/.
Итого
Обработка ошибок – это очень полезная и важная тема.
В JavaScript для этого используют следующие методы:
- Конструкция try..catch..finally – позволяет обработать ошибку в скрипте, как бы локализовать область с ошибкой.
- Возможны также и другие варианты try..catch или try..finally.
- Для генерации пользовательской ошибки используется оператор throw err.
Есть также и другие интересные приемы:
- Проброс исключения – catch(err) обрабатывает только те ошибки, которые вы хотели бы в нём увидеть, а вот остальные – пробрасывать дальше через throw err. Определить, нужная ли это ошибка, можно, по свойству name.
- Оборачивание исключений – эта некая функция, которая позволяет обернуть исключения и пробросить их дальше.
- В window.onerror можно присвоить специальную функцию, которая выполнится при любой ошибке.
Задача
Сравните два фрагмента кода.
- Первый использует finally для выполнения кода по выходу из try..catch:
Второй фрагмент просто ставит очистку ресурсов за try..catch:
Нужно, чтобы код финализации всегда выполнялся при выходе из блока try..catch и, таким образом, заканчивал начатую работу. Имеет ли здесь finally какое-то преимущество или оба фрагмента работают одинаково?
Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.
JavaScript — Обработка исключений
На этом уроке мы познакомимся с оператором обработки исключений try. catch , который предназначен для перехвата ошибок в некотором блоке кода и их обработки.
Применение оператора try. catch
Оператор обработки исключений try. catch обычно применяется в следующих ситуациях:
- Для некоторого участка кода, в котором может возникнуть ошибка;
- Для валидации (проверки) данных перед отправкой на сервер;
- Для использования в сложных случаях при написании кроссбраузерных приложений.
Теперь давайте рассмотрим, что же произойдет в сценарии на JavaScript при возникновении ошибки и для чего нужен оператор try. catch .
Если в сценарии на языке JavaScript появится ошибка, то дальнейшее выполнение программы будет прекращено. Т.е. инструкции (операторы), которые идут после ошибки выполняться не будут. Но если нам необходимо выполнение программы не прерывать, а переходить к следующей инструкции после той, где может возникнуть ошибка, то эту инструкцию необходимо заключить в оператор » try. catch «.
Синтаксис оператора try. catch
Принцип работы с оператором try. catch заключается в следующем:
Блок, в котором могут возникнуть ошибки, мы обрамляем фигурными скобками, и перед ним пишем ключевое слово try (с англ. попробовать). После этого блока пишем ключевое слово catch (с англ. поймать, ловить) и в круглых скобках указываем переменную, в которой будем хранить информацию об ошибке. Далее фигурными скобками обрамляем блок, предназначенный для обработки ошибок.
Принцип работы оператора try. catch
Оператор обработки исключений работает следующим образом:
Сначала он пытается выполнить все инструкции (операторы), указанные в блоке try . Если внутри блока try ошибки не возникает, то блок catch просто игнорируется, т.е. он не выполняется. В том случае если внутри блока try возникает ошибка, то оставшиеся операторы в этом блоке будут проигнорированы (т.е. они выполняться не будут) и сразу же начнут выполняться операторы внутри блока catch . Самое главное то, что инструкции, идущие после оператора try. catch , продолжат выполняться и работа программы не остановится.
В результате выполнения кода в 4 строчке произойдет ошибка, т.к. оператор documant браузером будет не определён. В этом случае 5 строчка браузером выполнена не будет, т.к. начнут выполняться операторы в блоке catch . В качестве параметра блока catch определим переменную e , в которой будет хранить информацию об ошибке. Данную переменную мы будем использовать в операторе alert для вывода сообщения об ошибке.
Блок finally
У оператора обработки исключений есть ещё один блок, который называется finally . Данный блок является не обязательным, и его можно использовать только при необходимости. Он не имеет параметров и в отличие от блока catch выполняется всегда, вне зависимости от того возникла ошибка или нет.
Но так как инструкции, идущие после оператора try. catch тоже выполняются всегда, то возникает вопрос: «Зачем нужен блок finally?»
На самом деле, инструкции, идущие после try. catch , выполняются не всегда. Это может произойти только в том случае, если произойдет ошибка в блоке catch . Когда это произойдёт, программа приостановит своё выполнение и операторы, идущие после конструкции try. catch выполняться не будут. Единственный блок инструкций (операторов), который будет выполняться – это блок finally .
В результате выполнения кода оператор alert , идущий после конструкции try. catch так и не будет выполнен.
Error handling, «try..catch»
No matter how great we are at programming, sometimes our scripts have errors. They may occur because of our mistakes, an unexpected user input, an erroneous server response, and for a thousand other reasons.
Usually, a script “dies” (immediately stops) in case of an error, printing it to console.
But there’s a syntax construct try..catch that allows us to “catch” errors so the script can, instead of dying, do something more reasonable.
The “try…catch” syntax
The try..catch construct has two main blocks: try , and then catch :
It works like this:
- First, the code in try <. >is executed.
- If there were no errors, then catch(err) is ignored: the execution reaches the end of try and goes on, skipping catch .
- If an error occurs, then the try execution is stopped, and control flows to the beginning of catch(err) . The err variable (we can use any name for it) will contain an error object with details about what happened.
So, an error inside the try <…>block does not kill the script – we have a chance to handle it in catch .
Let’s look at some examples.
An errorless example: shows alert (1) and (2) :
An example with an error: shows (1) and (3) :
For try..catch to work, the code must be runnable. In other words, it should be valid JavaScript.
It won’t work if the code is syntactically wrong, for instance it has unmatched curly braces:
The JavaScript engine first reads the code, and then runs it. The errors that occur on the reading phase are called “parse-time” errors and are unrecoverable (from inside that code). That’s because the engine can’t understand the code.
So, try..catch can only handle errors that occur in valid code. Such errors are called “runtime errors” or, sometimes, “exceptions”.
If an exception happens in “scheduled” code, like in setTimeout , then try..catch won’t catch it:
That’s because the function itself is executed later, when the engine has already left the try..catch construct.
To catch an exception inside a scheduled function, try..catch must be inside that function:
Error object
When an error occurs, JavaScript generates an object containing the details about it. The object is then passed as an argument to catch :
For all built-in errors, the error object has two main properties:
name Error name. For instance, for an undefined variable that’s «ReferenceError» . message Textual message about error details.
There are other non-standard properties available in most environments. One of most widely used and supported is:
stack Current call stack: a string with information about the sequence of nested calls that led to the error. Used for debugging purposes.
Optional “catch” binding
If we don’t need error details, catch may omit it:
Using “try…catch”
Let’s explore a real-life use case of try..catch .
As we already know, JavaScript supports the JSON.parse(str) method to read JSON-encoded values.
Usually it’s used to decode data received over the network, from the server or another source.
We receive it and call JSON.parse like this:
You can find more detailed information about JSON in the JSON methods, toJSON chapter.
If json is malformed, JSON.parse generates an error, so the script “dies”.
Should we be satisfied with that? Of course not!
This way, if something’s wrong with the data, the visitor will never know that (unless they open the developer console). And people really don’t like when something “just dies” without any error message.
Let’s use try..catch to handle the error:
Here we use the catch block only to show the message, but we can do much more: send a new network request, suggest an alternative to the visitor, send information about the error to a logging facility, … . All much better than just dying.
Throwing our own errors
What if json is syntactically correct, but doesn’t have a required name property?
Here JSON.parse runs normally, but the absence of name is actually an error for us.
To unify error handling, we’ll use the throw operator.
“Throw” operator
The throw operator generates an error.
Technically, we can use anything as an error object. That may be even a primitive, like a number or a string, but it’s better to use objects, preferably with name and message properties (to stay somewhat compatible with built-in errors).
JavaScript has many built-in constructors for standard errors: Error , SyntaxError , ReferenceError , TypeError and others. We can use them to create error objects as well.
Their syntax is:
For built-in errors (not for any objects, just for errors), the name property is exactly the name of the constructor. And message is taken from the argument.
Let’s see what kind of error JSON.parse generates:
As we can see, that’s a SyntaxError .
And in our case, the absence of name is an error, as users must have a name .
So let’s throw it:
In the line (*) , the throw operator generates a SyntaxError with the given message , the same way as JavaScript would generate it itself. The execution of try immediately stops and the control flow jumps into catch .
Now catch became a single place for all error handling: both for JSON.parse and other cases.
Rethrowing
In the example above we use try..catch to handle incorrect data. But is it possible that another unexpected error occurs within the try <. >block? Like a programming error (variable is not defined) or something else, not just this “incorrect data” thing.
Of course, everything’s possible! Programmers do make mistakes. Even in open-source utilities used by millions for decades – suddenly a bug may be discovered that leads to terrible hacks.
In our case, try..catch is meant to catch “incorrect data” errors. But by its nature, catch gets all errors from try . Here it gets an unexpected error, but still shows the same «JSON Error» message. That’s wrong and also makes the code more difficult to debug.
Fortunately, we can find out which error we get, for instance from its name :
The rule is simple:
Catch should only process errors that it knows and “rethrow” all others.
The “rethrowing” technique can be explained in more detail as:
- Catch gets all errors.
- In the catch(err) <. >block we analyze the error object err .
- If we don’t know how to handle it, we do throw err .
In the code below, we use rethrowing so that catch only handles SyntaxError :
The error throwing on line (*) from inside catch block “falls out” of try..catch and can be either caught by an outer try..catch construct (if it exists), or it kills the script.
So the catch block actually handles only errors that it knows how to deal with and “skips” all others.
The example below demonstrates how such errors can be caught by one more level of try..catch :
Here readData only knows how to handle SyntaxError , while the outer try..catch knows how to handle everything.
try…catch…finally
Wait, that’s not all.
The try..catch construct may have one more code clause: finally .
If it exists, it runs in all cases:
- after try , if there were no errors,
- after catch , if there were errors.
The extended syntax looks like this:
Try running this code:
The code has two ways of execution:
- If you answer “Yes” to “Make an error?”, then try -> catch -> finally .
- If you say “No”, then try -> finally .
The finally clause is often used when we start doing something and want to finalize it in any case of outcome.
For instance, we want to measure the time that a Fibonacci numbers function fib(n) takes. Naturally, we can start measuring before it runs and finish afterwards. But what if there’s an error during the function call? In particular, the implementation of fib(n) in the code below returns an error for negative or non-integer numbers.
The finally clause is a great place to finish the measurements no matter what.
Here finally guarantees that the time will be measured correctly in both situations – in case of a successful execution of fib and in case of an error in it:
You can check by running the code with entering 35 into prompt – it executes normally, finally after try . And then enter -1 – there will be an immediate error, and the execution will take 0ms . Both measurements are done correctly.
In other words, the function may finish with return or throw , that doesn’t matter. The finally clause executes in both cases.
Please note that result and diff variables in the code above are declared before try..catch .
Otherwise, if we declared let in try block, it would only be visible inside of it.
The finally clause works for any exit from try..catch . That includes an explicit return .
In the example below, there’s a return in try . In this case, finally is executed just before the control returns to the outer code.
The try..finally construct, without catch clause, is also useful. We apply it when we don’t want to handle errors here (let them fall through), but want to be sure that processes that we started are finalized.
In the code above, an error inside try always falls out, because there’s no catch . But finally works before the execution flow leaves the function.
Global catch
The information from this section is not a part of the core JavaScript.
Let’s imagine we’ve got a fatal error outside of try..catch , and the script died. Like a programming error or some other terrible thing.
Is there a way to react on such occurrences? We may want to log the error, show something to the user (normally they don’t see error messages), etc.
There is none in the specification, but environments usually provide it, because it’s really useful. For instance, Node.js has process.on(«uncaughtException») for that. And in the browser we can assign a function to the special window.onerror property, that will run in case of an uncaught error.
The role of the global handler window.onerror is usually not to recover the script execution – that’s probably impossible in case of programming errors, but to send the error message to developers.
There are also web-services that provide error-logging for such cases, like https://errorception.com or http://www.muscula.com.
They work like this:
- We register at the service and get a piece of JS (or a script URL) from them to insert on pages.
- That JS script sets a custom window.onerror function.
- When an error occurs, it sends a network request about it to the service.
- We can log in to the service web interface and see errors.
Summary
The try..catch construct allows to handle runtime errors. It literally allows to “try” running the code and “catch” errors that may occur in it.
There may be no catch section or no finally , so shorter constructs try..catch and try..finally are also valid.
Error objects have following properties:
- message – the human-readable error message.
- name – the string with error name (error constructor name).
- stack (non-standard, but well-supported) – the stack at the moment of error creation.
If an error object is not needed, we can omit it by using catch < instead of catch(err) < .
We can also generate our own errors using the throw operator. Technically, the argument of throw can be anything, but usually it’s an error object inheriting from the built-in Error class. More on extending errors in the next chapter.
Rethrowing is a very important pattern of error handling: a catch block usually expects and knows how to handle the particular error type, so it should rethrow errors it doesn’t know.