# Перехват ошибок, "try..catch" Как бы мы хорошо ни программировали, в коде бывают ошибки. Или, как их иначе называют, "исключительные ситуации" (исключения). Обычно скрипт при ошибке, как говорят, "падает", с выводом ошибки в консоль. Но бывают случаи, когда нам хотелось бы как-то контролировать ситуацию, чтобы скрипт не просто "упал", а сделал что-то разумное. Для этого в JavaScript есть замечательная конструкция `try..catch`. [cut] ## Конструкция try..catch Конструкция `try..catch` состоит из двух основных блоков: `try`, и затем `catch`: ```js try { // код ... } catch (err) { // обработка ошибки } ``` Работает она так:
  1. Выполняется код внутри блока `try`.
  2. Если в нём ошибок нет, то блок `catch(err)` игнорируется, то есть выполнение доходит до конца `try` и потом прыгает через `catch`.
  3. Если в нём возникнет ошибка, то выполнение `try` на ней прерывается, и управление прыгает в начало блока `catch(err)`. При этом переменная `err` (можно выбрать и другое название) будет содержать объект ошибки с подробной информацией о произошедшем.
**Таким образом, при ошибке в `try` скрипт не "падает", и мы получаем возможность обработать ошибку внутри `catch`.** Посмотрим это на примерах. [warn header="`try..catch` подразумевает, что код синтаксически верен"] Если грубо нарушена структура кода, например не закрыта фигурная скобка или где-то стоит лишняя запятая, то никакой `try..catch` здесь не поможет. Такие ошибки называются *синтаксическими*, интерпретатор не может понять такой код. Здесь же мы рассматриваем ошибки *семантические*, то есть происходящие в корректном коде, в процессе выполнения. [/warn] [warn header="`try..catch` работает только в синхронном коде"] Ошибку, которая произойдёт в коде, запланированном "на будущее", например, в `setTimeout`, `try..catch` не поймает: ```js //+ run try { setTimeout(function() { throw new Error(); // вылетит в консоль }, 1000); } catch (e) { alert( "не сработает" ); } ``` На момент запуска функции, назначенной через `setTimeout`, этот код уже завершится, интерпретатор выйдет из блока `try..catch`. Чтобы поймать ошибку внутри функции из `setTimeout`, и `try..catch` должен быть в той же функции. [/warn] ## Объект ошибки В примере выше мы видим объект ошибки. У него есть три основных свойства:
`name`
Тип ошибки. Например, при обращении к несуществующей переменной: `"ReferenceError"`.
`message`
Текстовое сообщение о деталях ошибки.
`stack`
Везде, кроме IE8-, есть также свойство `stack`, которое содержит строку с информацией о последовательности вызовов, которая привела к ошибке.
В зависимости от браузера, у него могут быть и дополнительные свойства, см. Error в MDN и Error в MSDN. ## Пример использования В JavaScript есть встроенный метод [JSON.parse(str)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse), который используется для чтения JavaScript-объектов (и не только) из строки. Обычно он используется для того, чтобы обрабатывать данные, полученные по сети, с сервера или из другого источника. Мы получаем их и вызываем метод `JSON.parse`, вот так: ```js //+ run var data = '{"name":"Вася", "age": 30}'; // строка с данными, полученная с сервера var user = JSON.parse(data); // преобразовали строку в объект // теперь user -- это JS-объект с данными из строки alert( user.name ); // Вася alert( user.age ); // 30 ``` Более детально формат JSON разобран в главе [](/json). **В случае, если данные некорректны, `JSON.parse` генерирует ошибку, то есть скрипт "упадёт".** Устроит ли нас такое поведение? Конечно нет! Получается, что если вдруг что-то не так с данными, то посетитель никогда (если, конечно, не откроет консоль) об этом не узнает. А люди очень-очень не любят, когда что-то "просто падает", без всякого объявления об ошибке. **Бывают ситуации, когда без `try..catch` не обойтись, это -- одна из таких.** Используем `try..catch`, чтобы обработать некорректный ответ: ```js //+ run var data = "Has Error"; // в данных ошибка try { var user = JSON.parse(data); // <-- ошибка при выполнении alert( user.name ); // не сработает } catch (e) { // ...выполнится catch alert( "Извините, в данных ошибка, мы попробуем получить их ещё раз" ); alert( e.name ); alert( e.message ); } ``` Здесь в `alert` только выводится сообщение, но область применения гораздо шире: можно повторять запрос, можно предлагать посетителю использовать альтернативный способ, можно отсылать информацию об ошибке на сервер... Свобода действий. ## Генерация своих ошибок Представим на минуту, что данные являются корректным JSON... Но в этом объекте нет нужного свойства `name`: ```js //+ run var data = '{ "age": 30 }'; // данные неполны try { var user = JSON.parse(data); // <-- выполнится без ошибок *!* alert( user.name ); // undefined */!* } catch (e) { // не выполнится alert( "Извините, в данных ошибка" ); } ``` Вызов `JSON.parse` выполнится без ошибок, но ошибка в данных есть. И, так как свойство `name` обязательно должно быть, то для нас это такие же некорректные данные как и `"Has Error"`. Для того, чтобы унифицировать и объединить обработку ошибок парсинга и ошибок в структуре, мы воспользуемся оператором `throw`. ### Оператор throw Оператор `throw` генерирует ошибку. Синтаксис: `throw <объект ошибки>`. Технически, в качестве объекта ошибки можно передать что угодно, это может быть даже не объект, а число или строка, но всё же лучше, чтобы это был объект, желательно -- совместимый со стандартным, то есть чтобы у него были как минимум свойства `name` и `message`. **В качестве конструктора ошибок можно использовать встроенный конструктор: `new Error(message)` или любой другой.** В JavaScript встроен ряд конструкторов для стандартных ошибок: `SyntaxError`, `ReferenceError`, `RangeError` и некоторые другие. Можно использовать и их, но только чтобы не было путаницы. В данном случае мы используем конструктор `new SyntaxError(message)`. Он создаёт ошибку того же типа, что и `JSON.parse`. ```js //+ run var data = '{ "age": 30 }'; // данные неполны try { var user = JSON.parse(data); // <-- выполнится без ошибок *!* if (!user.name) { throw new SyntaxError("Данные некорректны"); } */!* alert( user.name ); } catch (e) { alert( "Извините, в данных ошибка" ); } ``` Получилось, что блок `catch` -- единое место для обработки ошибок во всех случаях: когда ошибка выявляется при `JSON.parse` или позже. ## Проброс исключения В коде выше мы предусмотрели обработку ошибок, которые возникают при некорректных данных. Но может ли быть так, что возникнет какая-то другая ошибка? Конечно, может! Код -- это вообще мешок с ошибками, бывает даже так что библиотеку выкладывают в открытый доступ, она там 10 лет лежит, её смотрят миллионы людей и на 11й год находятся опаснейшие ошибки. Такова жизнь, таковы люди. Блок `catch` в нашем примере предназначен для обработки ошибок, возникающих при некорректных данных. Если же в него попала какая-то другая ошибка, то вывод сообщения о "некорректных данных" будет дезинформацией посетителя. **Ошибку, о которой `catch` не знает, он не должен обрабатывать.** Такая техника называется *"проброс исключения"*: в `catch(e)` мы анализируем объект ошибки, и если он нам не подходит, то делаем `throw e`. При этом ошибка "выпадает" из `try..catch` наружу. Далее она может быть поймана либо внешним блоком `try..catch` (если есть), либо "повалит" скрипт. В примере ниже `catch` обрабатывает только ошибки `SyntaxError`, а остальные -- выбрасывает дальше: ```js //+ run var data = '{ "name": "Вася", "age": 30 }'; // данные корректны try { var user = JSON.parse(data); if (!user.name) { throw new SyntaxError("Ошибка в данных"); } *!* blabla(); // произошла непредусмотренная ошибка */!* alert( user.name ); } catch (e) { *!* if (e.name == "SyntaxError") { alert( "Извините, в данных ошибка" ); } else { throw e; } */!* } ``` Заметим, что ошибка, которая возникла внутри блока `catch`, "выпадает" наружу, как если бы была в обычном коде. В следующем примере такие ошибки обрабатываются ещё одним, "более внешним" `try..catch`: ```js //+ run function readData() { var data = '{ "name": "Вася", "age": 30 }'; try { // ... *!* blabla(); // ошибка! */!* } catch (e) { // ... *!* if (e.name != 'SyntaxError') { throw e; // пробрасываем } */!* } } try { readData(); } catch (e) { *!* alert( "Поймал во внешнем catch: " + e ); // ловим */!* } ``` В примере выше `try..catch` внутри `readData` умеет обрабатывать только `SyntaxError`, а внешний -- все ошибки. Без внешнего проброшенная ошибка "вывалилась" бы в консоль, с остановкой скрипта. ## Оборачивание исключений И, для полноты картины -- последняя, самая продвинутая техника по работе с ошибками. Она, впрочем, является стандартной практикой во многих объектно-ориентированных языках. Цель функции `readData` в примере выше -- прочитать данные. При чтении могут возникать разные ошибки, не только `SyntaxError`, но и, возможно, к примеру, `URIError` (неправильное применение функций работы с URI), да и другие. Код, который вызвал `readData`, хотел бы иметь либо результат, либо информацию об ошибке. При этом очень важным является вопрос: обязан ли этот внешний код знать о всевозможных типах ошибок, которые могут возникать при чтении данных, и уметь перехватывать их? Обычно внешний код хотел бы работать "на уровень выше", и получать либо результат, либо "ошибку чтения данных", при этом какая именно ошибка произошла -- ему неважно. Ну, или, если будет важно, то хотелось бы иметь возможность это узнать, но обычно не требуется. Это важнейший общий подход к проектированию -- каждый участок функционала должен получать информацию на том уровне, который ему необходим. Мы его видим везде в грамотно построенном коде, но не всегда отдаём себе в этом отчёт. В данном случае, если при чтении данных происходит ошибка, то мы будем генерировать её в виде объекта `ReadError`, с соответствующим сообщением. А "исходную" ошибку -- на всякий случай тоже сохраним, присвоим в свойство `cause` (англ. -- причина). Выглядит это так: ```js //+ run function ReadError(message, cause) { this.message = message; this.cause = cause; this.name = 'ReadError'; this.stack = cause.stack; } function readData() { var data = '{ bad data }'; try { // ... JSON.parse(data); // ... } catch (e) { // ... if (e.name == 'URIError') { throw new ReadError("Ошибка в URI", e); } else if (e.name == 'SyntaxError') { *!* throw new ReadError("Синтаксическая ошибка в данных", e); */!* } else { throw e; // пробрасываем } } } try { readData(); } catch (e) { if (e.name == 'ReadError') { alert( e.message ); alert( e.cause ); // оригинальная ошибка-причина } else { throw e; } } ``` Этот подход называют "оборачиванием" исключения, поскольку мы берём ошибки "более низкого уровня" и "заворачиваем" их в `ReadError`, которая соответствует текущей задаче. ## Секция finally Конструкция `try..catch` может содержать ещё один блок: `finally`. Выглядит этот расширенный синтаксис так: ```js *!*try*/!* { .. пробуем выполнить код .. } *!*catch*/!*(e) { .. перехватываем исключение .. } *!*finally*/!* { .. выполняем всегда .. } ``` Секция `finally` не обязательна, но если она есть, то она выполняется всегда: Попробуйте запустить такой код? ```js //+ run try { alert( 'try' ); if (confirm('Сгенерировать ошибку?')) BAD_CODE(); } catch (e) { alert( 'catch' ); } finally { alert( 'finally' ); } ``` У него два варианта работы:
  1. Если вы ответите на вопрос "Сгенерировать ошибку?" утвердительно, то `try -> catch -> finally`.
  2. Если ответите отрицательно, то `try -> finally`.
**Секцию `finally` используют, чтобы завершить начатые операции при любом варианте развития событий.** Например, мы хотим подсчитать время на выполнение функции `sum(n)`, которая должна возвратить сумму чисел от `1` до `n` и работает рекурсивно: ```js //+ run function sum(n) { return n ? (n + sum(n - 1)) : 0; } var n = +prompt('Введите n?', 100); var start = new Date(); try { var result = sum(n); } catch (e) { result = 0; *!* } finally { var diff = new Date() - start; } */!* alert( result ? result : 'была ошибка' ); alert( "Выполнение заняло " + diff ); ``` Здесь секция `finally` гарантирует, что время будет подсчитано в любых ситуациях -- при ошибке в `sum` или без неё. Вы можете проверить это, запустив код с указанием `n=100` -- будет без ошибки, `finally` выполнится после `try`, а затем с `n=100000` -- будет ошибка из-за слишком глубокой рекурсии, управление прыгнет в `finally` после `catch`. [smart header="`finally` и `return`"] Блок `finally` срабатывает при *любом* выходе из `try..catch`, в том числе и `return`. В примере ниже, из `try` происходит `return`, но `finally` получает управление до того, как контроль возвращается во внешний код. ```js //+ run function func() { try { // сразу вернуть значение return 1; } catch (e) { /* ... */ } finally { *!* alert( 'finally' ); */!* } } alert( func() ); // сначала finally, потом 1 ``` Если внутри `try` были начаты какие-то процессы, которые нужно завершить по окончании работы, во в `finally` это обязательно будет сделано. Кстати, для таких случаев иногда используют `try..finally` вообще без `catch`: ```js //+ run function func() { try { return 1; } finally { alert( 'Вызов завершён' ); } } alert( func() ); // сначала finally, потом 1 ``` В примере выше `try..finally` вообще не обрабатывает ошибки. Задача в другом -- выполнить код при любом выходе из `try` -- с ошибкой ли, без ошибок или через `return`. [/smart] ## Последняя надежда: window.onerror Допустим, ошибка произошла вне блока `try..catch` или выпала из `try..catch` наружу, во внешний код. Скрипт упал. Можно ли как-то узнать о том, что произошло? Да, конечно. В браузере существует специальное свойство `window.onerror`, если в него записать функцию, то она выполнится и получит в аргументах сообщение ошибки, текущий URL и номер строки, откуда "выпала" ошибка. Необходимо лишь позаботиться, чтобы функция была назначена заранее. Например: ```html ``` Как правило, роль `window.onerror` заключается в том, чтобы не оживить скрипт -- скорее всего, это уже невозможно, а в том, чтобы отослать сообщение об ошибке на сервер, где разработчики о ней узнают. Существуют даже специальные веб-сервисы, которые предоставляют скрипты для отлова и аналитики таких ошибок, например: [](https://errorception.com/) или [](http://www.muscula.com/). ## Итого Обработка ошибок -- большая и важная тема. В JavaScript для этого предусмотрены: Кроме того, мы рассмотрели некоторые важные приёмы: