Перехват ошибок, «try..catch»
Материал на этой странице устарел, поэтому скрыт из оглавления сайта.
Более новая информация по этой теме находится на странице https://learn.javascript.ru/try-catch.
Как бы мы хорошо ни программировали, в коде бывают ошибки. Или, как их иначе называют, «исключительные ситуации» (исключения).
Обычно скрипт при ошибке, как говорят, «падает», с выводом ошибки в консоль.
Но бывают случаи, когда нам хотелось бы как-то контролировать ситуацию, чтобы скрипт не просто «упал», а сделал что-то разумное.
Для этого в JavaScript есть замечательная конструкция try..catch .
Конструкция try…catch
Конструкция try..catch состоит из двух основных блоков: try , и затем catch :
try < // код . >catch (err) < // обработка ошибки >
Работает она так:
- Выполняется код внутри блока try .
- Если в нём ошибок нет, то блок catch(err) игнорируется, то есть выполнение доходит до конца try и потом прыгает через catch .
- Если в нём возникнет ошибка, то выполнение try на ней прерывается, и управление прыгает в начало блока catch(err) . При этом переменная err (можно выбрать и другое название) будет содержать объект ошибки с подробной информацией о произошедшем.
Таким образом, при ошибке в try скрипт не «падает», и мы получаем возможность обработать ошибку внутри catch .
Посмотрим это на примерах.
-
Пример без ошибок: при запуске сработают alert (1) и (2) :
try < alert('Начало блока try'); // (1) catch(e) < alert('Блок catch не получит управление, так как нет ошибок'); // (3) >alert("Потом код продолжит выполнение. ");
try < alert('Начало блока try'); // (1) catch(e) < alert('Ошибка ' + e.name + ":" + e.message + "\n" + e.stack); // (3) alert("Потом код продолжит выполнение. ");
try..catch подразумевает, что код синтаксически верен
Если грубо нарушена структура кода, например не закрыта фигурная скобка или где-то стоит лишняя запятая, то никакой try..catch здесь не поможет. Такие ошибки называются синтаксическими, интерпретатор не может понять такой код.
Здесь же мы рассматриваем ошибки семантические, то есть происходящие в корректном коде, в процессе выполнения.
try..catch работает только в синхронном коде
Ошибку, которая произойдёт в коде, запланированном «на будущее», например в setTimeout , try..catch не поймает:
try < setTimeout(function() < throw new Error(); // вылетит в консоль >, 1000); > catch (e)
На момент запуска функции, назначенной через setTimeout , этот код уже завершится, интерпретатор выйдет из блока try..catch .
Чтобы поймать ошибку внутри функции из setTimeout , и try..catch должен быть в той же функции.
Объект ошибки
В примере выше мы видим объект ошибки. У него есть три основных свойства:
name Тип ошибки. Например, при обращении к несуществующей переменной: "ReferenceError" . message Текстовое сообщение о деталях ошибки. stack Везде, кроме IE8-, есть также свойство stack , которое содержит строку с информацией о последовательности вызовов, которая привела к ошибке.
В зависимости от браузера у него могут быть и дополнительные свойства, см. Error в MDN и Error в MSDN.
Пример использования
В JavaScript есть встроенный метод JSON.parse(str), который используется для чтения JavaScript-объектов (и не только) из строки.
Обычно он используется для того, чтобы обрабатывать данные, полученные по сети, с сервера или из другого источника.
Мы получаем их и вызываем метод JSON.parse , вот так:
var data = ''; // строка с данными, полученная с сервера var user = JSON.parse(data); // преобразовали строку в объект // теперь user -- это JS-объект с данными из строки alert( user.name ); // Вася alert( user.age ); // 30
Более детально формат JSON разобран в главе Формат JSON, метод toJSON.
В случае, если данные некорректны, JSON.parse генерирует ошибку, то есть скрипт «упадёт».
Устроит ли нас такое поведение? Конечно нет!
Получается, что если вдруг что-то не так с данными, то посетитель никогда (если, конечно, не откроет консоль) об этом не узнает.
А люди очень-очень не любят, когда что-то «просто падает», без всякого объявления об ошибке.
Бывают ситуации, когда без try..catch не обойтись, это – одна из таких.
Используем try..catch , чтобы обработать некорректный ответ:
var data = "Has Error"; // в данных ошибка try < var user = JSON.parse(data); // catch (e) < // . выполнится catch alert( "Извините, в данных ошибка, мы попробуем получить их ещё раз" ); alert( e.name ); alert( e.message ); >
Здесь в alert только выводится сообщение, но область применения гораздо шире: можно повторять запрос, можно предлагать посетителю использовать альтернативный способ, можно отсылать информацию об ошибке на сервер… Свобода действий.
Генерация своих ошибок
Представим на минуту, что данные являются корректным JSON… Но в этом объекте нет нужного свойства name :
var data = '< "age": 30 >'; // данные неполны try < var user = JSON.parse(data); // catch (e) < // не выполнится alert( "Извините, в данных ошибка" ); >
Вызов JSON.parse выполнится без ошибок, но ошибка в данных есть. И, так как свойство name обязательно должно быть, то для нас это такие же некорректные данные, как и "Has Error" .
Для того, чтобы унифицировать и объединить обработку ошибок парсинга и ошибок в структуре, мы воспользуемся оператором throw .
Оператор throw
Оператор throw генерирует ошибку.
Технически в качестве объекта ошибки можно передать что угодно, это может быть даже не объект, а число или строка, но всё же лучше, чтобы это был объект, желательно – совместимый со стандартным, то есть чтобы у него были как минимум свойства name и message .
В качестве конструктора ошибок можно использовать встроенный конструктор: new Error(message) или любой другой.
В JavaScript встроен ряд конструкторов для стандартных ошибок: SyntaxError , ReferenceError , RangeError и некоторые другие. Можно использовать и их, но только чтобы не было путаницы.
В данном случае мы используем конструктор new SyntaxError(message) . Он создаёт ошибку того же типа, что и JSON.parse .
var data = '< "age": 30 >'; // данные неполны try < var user = JSON.parse(data); // alert( user.name ); > catch (e)
Получилось, что блок catch – единое место для обработки ошибок во всех случаях: когда ошибка выявляется при JSON.parse или позже.
Проброс исключения
В коде выше мы предусмотрели обработку ошибок, которые возникают при некорректных данных. Но может ли быть так, что возникнет какая-то другая ошибка?
Конечно, может! Код – это вообще мешок с ошибками, бывает даже так, что библиотеку выкладывают в открытый доступ, она там 10 лет лежит, её смотрят миллионы людей и на 11-й год находятся опаснейшие ошибки. Такова жизнь, таковы люди.
Блок catch в нашем примере предназначен для обработки ошибок, возникающих при некорректных данных. Если же в него попала какая-то другая ошибка, то вывод сообщения о «некорректных данных» будет дезинформацией посетителя.
Ошибку, о которой catch не знает, он не должен обрабатывать.
Такая техника называется «проброс исключения»: в catch(e) мы анализируем объект ошибки, и если он нам не подходит, то делаем throw e .
При этом ошибка «выпадает» из try..catch наружу. Далее она может быть поймана либо внешним блоком try..catch (если есть), либо «повалит» скрипт.
В примере ниже catch обрабатывает только ошибки SyntaxError , а остальные – выбрасывает дальше:
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 :
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 (англ. – причина).
Выглядит это так:
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 .
Выглядит этот расширенный синтаксис так:
try < .. пробуем выполнить код .. >catch(e) < .. перехватываем исключение .. >finally
Секция finally не обязательна, но если она есть, то она выполняется всегда:
- после блока try , если ошибок не было,
- после catch , если они были.
Попробуйте запустить такой код?
Обработка ошибок, "try..catch"
Неважно, насколько мы хороши в программировании, иногда наши скрипты содержат ошибки. Они могут возникать из-за наших промахов, неожиданного ввода пользователя, неправильного ответа сервера и по тысяче других причин.
Обычно скрипт в случае ошибки «падает» (сразу же останавливается), с выводом ошибки в консоль.
Но есть синтаксическая конструкция try..catch , которая позволяет «ловить» ошибки и вместо падения делать что-то более осмысленное.
Синтаксис «try…catch»
Конструкция try..catch состоит из двух основных блоков: try , и затем catch :
try < // код. >catch (err) < // обработка ошибки >
Работает она так:
- Сначала выполняется код внутри блока try .
- Если в нём нет ошибок, то блок catch(err) игнорируется: выполнение доходит до конца try и потом далее, полностью пропуская catch .
- Если же в нём возникает ошибка, то выполнение try прерывается, и поток управления переходит в начало catch(err) . Переменная err (можно использовать любое имя) содержит объект ошибки с подробной информацией о произошедшем.
Таким образом, при ошибке в блоке try скрипт не «падает», и мы получаем возможность обработать ошибку внутри catch .
Давайте рассмотрим примеры.
-
Пример без ошибок: выведет alert (1) и (2) :
try < alert('Начало блока try'); // (1) catch(err) < alert('Catch игнорируется, так как нет ошибок'); // (3) >
try < alert('Начало блока try'); // (1) catch(err) < alert(`Возникла ошибка!`); // (3)
try..catch работает только для ошибок, возникающих во время выполнения кода
Чтобы try..catch работал, код должен быть выполнимым. Другими словами, это должен быть корректный JavaScript-код.
Он не сработает, если код синтаксически неверен, например, содержит несовпадающее количество фигурных скобок:
try < catch(e)
JavaScript-движок сначала читает код, а затем исполняет его. Ошибки, которые возникают во время фазы чтения, называются ошибками парсинга. Их нельзя обработать (изнутри этого кода), потому что движок не понимает код.
Таким образом, try..catch может обрабатывать только ошибки, которые возникают в корректном коде. Такие ошибки называют «ошибками во время выполнения», а иногда «исключениями».
try..catch работает синхронно
Исключение, которое произойдёт в коде, запланированном «на будущее», например в setTimeout , try..catch не поймает:
try < setTimeout(function() < noSuchVariable; // скрипт упадёт тут >, 1000); > catch (e)
Это потому, что функция выполняется позже, когда движок уже покинул конструкцию try..catch .
Чтобы поймать исключение внутри запланированной функции, try..catch должен находиться внутри самой этой функции:
setTimeout(function() < try < noSuchVariable; // try..catch обрабатывает ошибку! >catch < alert( "ошибка поймана!" ); >>, 1000);
Объект ошибки
Когда возникает ошибка, JavaScript генерирует объект, содержащий её детали. Затем этот объект передаётся как аргумент в блок catch :
try < // . >catch(err) < //
Для всех встроенных ошибок этот объект имеет два основных свойства:
name Имя ошибки. Например, для неопределённой переменной это "ReferenceError" . message Текстовое сообщение о деталях ошибки.
В большинстве окружений доступны и другие, нестандартные свойства. Одно из самых широко используемых и поддерживаемых – это:
stack Текущий стек вызова: строка, содержащая информацию о последовательности вложенных вызовов, которые привели к ошибке. Используется в целях отладки.
try < lalala; // ошибка, переменная не определена! >catch(err) < alert(err.name); // ReferenceError alert(err.message); // lalala is not defined alert(err.stack); // ReferenceError: lalala is not defined at (. стек вызовов) // Можем также просто вывести ошибку целиком // Ошибка приводится к строке вида "name: message" alert(err); // ReferenceError: lalala is not defined >
Блок «catch» без переменной
Новая возможность
Эта возможность была добавлена в язык недавно. В старых браузерах может понадобиться полифил.
Если нам не нужны детали ошибки, в catch можно её пропустить:
try < // . >catch < //
Использование «try…catch»
Давайте рассмотрим реальные случаи использования try..catch .
Как мы уже знаем, JavaScript поддерживает метод JSON.parse(str) для чтения JSON.
Обычно он используется для декодирования данных, полученных по сети, от сервера или из другого источника.
Мы получаем их и вызываем JSON.parse вот так:
let json = ''; // данные с сервера let user = JSON.parse(json); // преобразовали текстовое представление в JS-объект // теперь user - объект со свойствами из строки alert( user.name ); // John alert( user.age ); // 30
Вы можете найти более детальную информацию о JSON в главе Формат JSON, метод toJSON.
Если json некорректен, JSON.parse генерирует ошибку, то есть скрипт «падает».
Устроит ли нас такое поведение? Конечно нет!
Получается, что если вдруг что-то не так с данными, то посетитель никогда (если, конечно, не откроет консоль) об этом не узнает. А люди очень не любят, когда что-то «просто падает» без всякого сообщения об ошибке.
Давайте используем try..catch для обработки ошибки:
let json = "< некорректный JSON >"; try < let user = JSON.parse(json); // catch (e) < // . выполнение прыгает сюда alert( "Извините, в данных ошибка, мы попробуем получить их ещё раз." ); alert( e.name ); alert( e.message ); >
Здесь мы используем блок catch только для вывода сообщения, но мы также можем сделать гораздо больше: отправить новый сетевой запрос, предложить посетителю альтернативный способ, отослать информацию об ошибке на сервер для логирования, … Всё лучше, чем просто «падение».
Генерация собственных ошибок
Что если json синтаксически корректен, но не содержит необходимого свойства name ?
let json = '< "age": 30 >'; // данные неполны try < let user = JSON.parse(json); // catch (e)
Здесь JSON.parse выполнится без ошибок, но на самом деле отсутствие свойства name для нас ошибка.
Для того, чтобы унифицировать обработку ошибок, мы воспользуемся оператором throw .
Оператор «throw»
Оператор throw генерирует ошибку.
throw
Технически в качестве объекта ошибки можно передать что угодно. Это может быть даже примитив, число или строка, но всё же лучше, чтобы это был объект, желательно со свойствами name и message (для совместимости со встроенными ошибками).
В JavaScript есть множество встроенных конструкторов для стандартных ошибок: Error , SyntaxError , ReferenceError , TypeError и другие. Можно использовать и их для создания объектов ошибки.
let error = new Error(message); // или let error = new SyntaxError(message); let error = new ReferenceError(message); // .
Для встроенных ошибок (не для любых объектов, только для ошибок), свойство name – это в точности имя конструктора. А свойство message берётся из аргумента.
let error = new Error("Ого, ошибка! o_O"); alert(error.name); // Error alert(error.message); // Ого, ошибка! o_O
Давайте посмотрим, какую ошибку генерирует JSON.parse :
try < JSON.parse("< некорректный json o_O >"); > catch(e) < alert(e.name); // SyntaxError alert(e.message); // Unexpected token b in JSON at position 2 >
Как мы видим, это SyntaxError .
В нашем случае отсутствие свойства name – это ошибка, ведь пользователи должны иметь имена.
let json = '< "age": 30 >'; // данные неполны try < let user = JSON.parse(json); // alert( user.name ); > catch(e) < alert( "JSON Error: " + e.message ); // JSON Error: Данные неполны: нет имени >
В строке (*) оператор throw генерирует ошибку SyntaxError с сообщением message . Точно такого же вида, как генерирует сам JavaScript. Выполнение блока try немедленно останавливается, и поток управления прыгает в catch .
Теперь блок catch становится единственным местом для обработки всех ошибок: и для JSON.parse и для других случаев.
Проброс исключения
В примере выше мы использовали try..catch для обработки некорректных данных. А что, если в блоке try <. >возникнет другая неожиданная ошибка? Например, программная (неопределённая переменная) или какая-то ещё, а не ошибка, связанная с некорректными данными.
let json = '< "age": 30 >'; // данные неполны try < user = JSON.parse(json); // catch(err) < alert("JSON Error: " + err); // JSON Error: ReferenceError: user is not defined // (не JSON ошибка на самом деле) >
Конечно, возможно все! Программисты совершают ошибки. Даже в утилитах с открытым исходным кодом, используемых миллионами людей на протяжении десятилетий – вдруг может быть обнаружена ошибка, которая приводит к ужасным взломам.
В нашем случае try..catch предназначен для выявления ошибок, связанных с некорректными данными. Но по своей природе catch получает все свои ошибки из try . Здесь он получает неожиданную ошибку, но всё также показывает то же самое сообщение "JSON Error" . Это неправильно и затрудняет отладку кода.
К счастью, мы можем выяснить, какую ошибку мы получили, например, по её свойству name :
try < user = < /*. */ >; > catch(e) < alert(e.name); // "ReferenceError" из-за неопределённой переменной >
Есть простое правило:
Блок catch должен обрабатывать только те ошибки, которые ему известны, и «пробрасывать» все остальные.
Техника «проброс исключения» выглядит так:
- Блок catch получает все ошибки.
- В блоке catch(err) мы анализируем объект ошибки err .
- Если мы не знаем как её обработать, тогда делаем throw err .
В коде ниже мы используем проброс исключения, catch обрабатывает только SyntaxError :
let json = '< "age": 30 >'; // данные неполны try < let user = JSON.parse(json); if (!user.name) < throw new SyntaxError("Данные неполны: нет имени"); >blabla(); // неожиданная ошибка alert( user.name ); > catch(e) < if (e.name == "SyntaxError") < alert( "JSON Error: " + e.message ); >else < throw e; // проброс (*) >>
Ошибка в строке (*) из блока catch «выпадает наружу» и может быть поймана другой внешней конструкцией try..catch (если есть), или «убьёт» скрипт.
Таким образом, блок catch фактически обрабатывает только те ошибки, с которыми он знает, как справляться, и пропускает остальные.
Пример ниже демонстрирует, как такие ошибки могут быть пойманы с помощью ещё одного уровня try..catch :
function readData() < let json = '< "age": 30 >'; try < // . blabla(); // ошибка! >catch (e) < // . if (e.name != 'SyntaxError') < throw e; // проброс исключения (не знаю как это обработать) >> > try < readData(); >catch (e) < alert( "Внешний catch поймал: " + e ); // поймал! >
Здесь readData знает только, как обработать SyntaxError , тогда как внешний блок try..catch знает, как обработать всё.
try…catch…finally
Подождите, это ещё не всё.
Конструкция try..catch может содержать ещё одну секцию: finally .
Если секция есть, то она выполняется в любом случае:
- после try , если не было ошибок,
- после catch , если ошибки были.
Расширенный синтаксис выглядит следующим образом:
try < . пробуем выполнить код. >catch(e) < . обрабатываем ошибки . >finally
Попробуйте запустить такой код:
try < alert( 'try' ); if (confirm('Сгенерировать ошибку?')) BAD_CODE(); >catch (e) < alert( 'catch' ); >finally
У кода есть два пути выполнения:
- Если вы ответите на вопрос «Сгенерировать ошибку?» утвердительно, то try -> catch -> finally .
- Если ответите отрицательно, то try -> finally .
Секцию finally часто используют, когда мы начали что-то делать и хотим завершить это вне зависимости от того, будет ошибка или нет.
Например, мы хотим измерить время, которое занимает функция чисел Фибоначчи fib(n) . Естественно, мы можем начать измерения до того, как функция начнёт выполняться и закончить после. Но что делать, если при вызове функции возникла ошибка? В частности, реализация fib(n) в коде ниже возвращает ошибку для отрицательных и для нецелых чисел.
Секция finally отлично подходит для завершения измерений несмотря ни на что.
Здесь finally гарантирует, что время будет измерено корректно в обеих ситуациях – и в случае успешного завершения fib и в случае ошибки:
let num = +prompt("Введите положительное целое число?", 35) let diff, result; function fib(n) < if (n < 0 || Math.trunc(n) != n) < throw new Error("Должно быть целое неотрицательное число"); >return n let start = Date.now(); try < result = fib(num); >catch (e) < result = 0; >finally < diff = Date.now() - start; >alert(result || "возникла ошибка"); alert( `Выполнение заняло $ms` );
Вы можете это проверить, запустив этот код и введя 35 в prompt – код завершится нормально, finally выполнится после try . А затем введите -1 – незамедлительно произойдёт ошибка, выполнение займёт 0ms . Оба измерения выполняются корректно.
Другими словами, неважно как завершилась функция: через return или throw . Секция finally срабатывает в обоих случаях.
Переменные внутри try..catch..finally локальны
Обратите внимание, что переменные result и diff в коде выше объявлены до try..catch .
Если переменную объявить в блоке, например, в try , то она не будет доступна после него.
finally и return
Блок finally срабатывает при любом выходе из try..catch , в том числе и return .
В примере ниже из try происходит return , но finally получает управление до того, как контроль возвращается во внешний код.
function func() < try < return 1; >catch (e) < /* . */ >finally < alert( 'finally' ); >> alert( func() ); // сначала срабатывает alert из finally, а затем этот код
try..finally
Конструкция try..finally без секции catch также полезна. Мы применяем её, когда не хотим здесь обрабатывать ошибки (пусть выпадут), но хотим быть уверены, что начатые процессы завершились.
function func() < // начать делать что-то, что требует завершения (например, измерения) try < // . >finally < // завершить это, даже если все упадёт >>
В приведённом выше коде ошибка всегда выпадает наружу, потому что тут нет блока catch . Но finally отрабатывает до того, как поток управления выйдет из функции.
Глобальный catch
Зависит от окружения
Информация из данной секции не является частью языка JavaScript.
Давайте представим, что произошла фатальная ошибка (программная или что-то ещё ужасное) снаружи try..catch , и скрипт упал.
Существует ли способ отреагировать на такие ситуации? Мы можем захотеть залогировать ошибку, показать что-то пользователю (обычно они не видят сообщение об ошибке) и т.д.
Такого способа нет в спецификации, но обычно окружения предоставляют его, потому что это весьма полезно. Например, в Node.js для этого есть process.on("uncaughtException") . А в браузере мы можем присвоить функцию специальному свойству window.onerror, которая будет вызвана в случае необработанной ошибки.
window.onerror = function(message, url, line, col, error) < // . >;
message Сообщение об ошибке. url URL скрипта, в котором произошла ошибка. line , col Номера строки и столбца, в которых произошла ошибка. error Объект ошибки.
Роль глобального обработчика window.onerror обычно заключается не в восстановлении выполнения скрипта – это скорее всего невозможно в случае программной ошибки, а в отправке сообщения об ошибке разработчикам.
Существуют также веб-сервисы, которые предоставляют логирование ошибок для таких случаев, такие как https://errorception.com или http://www.muscula.com.
Они работают так:
- Мы регистрируемся в сервисе и получаем небольшой JS-скрипт (или URL скрипта) от них для вставки на страницы.
- Этот JS-скрипт ставит свою функцию window.onerror .
- Когда возникает ошибка, она выполняется и отправляет сетевой запрос с информацией о ней в сервис.
- Мы можем войти в веб-интерфейс сервиса и увидеть ошибки.
Итого
Конструкция try..catch позволяет обрабатывать ошибки во время исполнения кода. Она позволяет запустить код и перехватить ошибки, которые могут в нём возникнуть.
try < // исполняем код >catch(err) < // если случилась ошибка, прыгаем сюда // err - это объект ошибки >finally < // выполняется всегда после try/catch >
Секций catch или finally может не быть, то есть более короткие конструкции try..catch и try..finally также корректны.
Объекты ошибок содержат следующие свойства:
- message – понятное человеку сообщение.
- name – строка с именем ошибки (имя конструктора ошибки).
- stack (нестандартное, но хорошо поддерживается) – стек на момент ошибки.
Если объект ошибки не нужен, мы можем пропустить его, используя catch < вместо catch(err) < .
Мы можем также генерировать собственные ошибки, используя оператор throw . Аргументом throw может быть что угодно, но обычно это объект ошибки, наследуемый от встроенного класса Error . Подробнее о расширении ошибок см. в следующей главе.
Проброс исключения – это очень важный приём обработки ошибок: блок catch обычно ожидает и знает, как обработать определённый тип ошибок, поэтому он должен пробрасывать дальше ошибки, о которых он не знает.
Даже если у нас нет try..catch , большинство сред позволяют настроить «глобальный» обработчик ошибок, чтобы ловить ошибки, которые «выпадают наружу». В браузере это window.onerror .
try . catch
Позволяет обработать ситуацию, когда код выбрасывает ошибку, и не сломать приложение.
Время чтения: 7 мин
Открыть/закрыть навигацию по статье
Контрибьюторы:
Обновлено 20 декабря 2021
Кратко
Скопировать ссылку "Кратко" Скопировано
Конструкция try . . . catch позволяет выполнить произвольный код, но если в нем произошла ошибка, то программа не остановит своё выполнение, а перейдёт в блок catch , где ошибку можно обработать.
Как понять
Скопировать ссылку "Как понять" Скопировано
Ошибки в программах это очень неприятно, но никто не застрахован от их появления. К тому же, ошибки могут появляться в тех ситуациях, которые не зависят от нас самих. Например, пользователь неправильно воспользовался программой. Поэтому в языке необходимы конструкции, которые позволяют выполнить произвольный код, но в случае ошибки дать возможность её обработать.
В JavaScript ситуация ещё сложнее. Если во время работы скрипта возникла ошибка и она не была обработана, то выполнение останавливается, и программа больше не работает.
Конструкция try . . . catch делает программы стабильнее — в случае ошибки мы можем продолжить выполнение. Мы можем написать любой синтаксически верный код и, если он выполнится без ошибок, то отлично. Если же что-то пойдёт не так, то выполнится код, написанный в catch .
Представьте, что вы тренируетесь отбивать мяч в бейсбол. У вас есть партнёр, который будет ловить мяч, в случае вашей ошибки, если отбить мяч не удастся. В большинстве попыток у вас все будет получаться хорошо, но если же случится промах, то второй игрок поймает мяч и вернёт его на место, чтобы можно было попробовать снова.
С пойманной в catch ошибкой можно поступить как угодно: отправить данные в специальный логгер, обработать данные из неё и даже выбросить новую ошибку, которая может быть поймана в другом месте и т.д.
Как пишется
Скопировать ссылку "Как пишется" Скопировано
Базовый случай
Скопировать ссылку "Базовый случай" Скопировано
Чтобы использовать try . . . catch , необходимо в блоке try написать код, который нужно исполнить, а в блоке catch написать, что делать в случае ошибки.
try someFunction() anotherFunction()> catch (err) console.log('Поймали ошибку! Вот она: ', err.message)>try someFunction() anotherFunction() > catch (err) console.log('Поймали ошибку! Вот она: ', err.message) >
Если в блоке try не произошло ошибок, то код в блоке catch не выполнится.
Важно помнить, что код в try должен быть синтаксически верным. Если написать невалидный код (например, не закрыть фигурные скобки), то скрипт не запустится, потому что JavaScript не поймёт код. Ошибки, которые обработает блок catch , будут ошибками во время выполнения программы.
В случае ошибки выполнение в блоке try прерывается и сразу же переходит в блок catch . После него скрипт продолжит своё выполнение, как и прежде.
try const six = 6 // 1. Создаём константу console.log(six) // 2. Выведет 6 six = 7 // Ошибка! Присваиваем новое значение в const // с этого места управление переходит в catch const nine = 9 // не выполнится console.log(six + nine) // и это тоже не исполнится> catch (err) console.log('Поймали ошибку!') // 3. Обработали ошибку> console.log('Что ж, можно и продолжать') // 4. Будет выполняться дальшеtry const six = 6 // 1. Создаём константу console.log(six) // 2. Выведет 6 six = 7 // Ошибка! Присваиваем новое значение в const // с этого места управление переходит в catch const nine = 9 // не выполнится console.log(six + nine) // и это тоже не исполнится > catch (err) console.log('Поймали ошибку!') // 3. Обработали ошибку > console.log('Что ж, можно и продолжать') // 4. Будет выполняться дальше

finally
Скопировать ссылку "finally" Скопировано
Рассмотрим ситуацию, когда в случае успеха или неудачи выполнения какого-то участка кода нам необходимо проводить какие-то действия, чтобы корректно завершить работу скрипта.
try // подключаемся к вебсокету, но в конце нужно обязательно отключиться webSocket.connect('ws://. ') callMayThrowError()> catch (err) . > // Пробуем отключаться после try. catchwebSocket.disconnect('ws://. ')try // подключаемся к вебсокету, но в конце нужно обязательно отключиться webSocket.connect('ws://. ') callMayThrowError() > catch (err) . > // Пробуем отключаться после try. catch webSocket.disconnect('ws://. ')
Казалось бы никаких проблем с этим кодом быть не должно, ведь неважно выполнится код в блоке try правильно или попадёт в catch , следующая строчка должна выполниться. Однако возможна ситуация, что в блоке catch тоже возникнет ошибка, и тогда выполнение следующей строчки уже не случится.
function doSomeWithError(e) throw new Error('new error')> try // подключаемся к вебсокету, но в конце нужно обязательно отключиться webSocket.connect('ws://. ') callMayThrowError()> catch (err) // Здесь тоже может возникнуть ошибка doSomeWithError(err)> // В случае ошибки эта строчка уже не выполнитсяwebSocket.disconnect('ws://. ')function doSomeWithError(e) throw new Error('new error') > try // подключаемся к вебсокету, но в конце нужно обязательно отключиться webSocket.connect('ws://. ') callMayThrowError() > catch (err) // Здесь тоже может возникнуть ошибка doSomeWithError(err) > // В случае ошибки эта строчка уже не выполнится webSocket.disconnect('ws://. ')
Как же тогда гарантированно освободить ресурсы при любом исходе выполнения?
В конструкцию try . . . catch можно добавить блок finally , который выполнится после блоков try и catch . Неважно какой код выполнился в предыдущих блоках, после их завершения (даже если из catch была выброшена новая ошибка) исполнится код в блоке finally .
try webSocket.connect('ws://. ') callMayThrowError()> catch (err) // Здесь тоже может возникнуть ошибка doSomeWithError(err)> finally // Выполнится всегда webSocket.disconnect('ws://. ')>try webSocket.connect('ws://. ') callMayThrowError() > catch (err) // Здесь тоже может возникнуть ошибка doSomeWithError(err) > finally // Выполнится всегда webSocket.disconnect('ws://. ') >

Наличие блока finally необязательно. finally можно использовать и без блока catch .
try // Отправить данные на сервер, здесь нам неважна обработка ошибки sendData()> finally // Закрыть соединение при любом результате closeConnection()>try // Отправить данные на сервер, здесь нам неважна обработка ошибки sendData() > finally // Закрыть соединение при любом результате closeConnection() >
Ошибки в catch
Скопировать ссылку "Ошибки в catch" Скопировано
Может возникнуть ситуация, когда скрипт обработки ошибки тоже может сломаться. При этом могут возникнуть случаи, когда мы хотим намеренно выбросить новую ошибку из catch , или пробросить текущую, чтобы её обработать в другом месте.
Хорошей практикой считается обрабатывать в модуле только те ошибки, которые связаны непосредственно с ним, а все остальные пробрасывать дальше.
// parse-module.js // Есть свой тип ошибкиclass ParsingError extends Error . > function parse(data) try parseData(data) > catch (err) if (err.name !== 'ParsingError') // Другой тип ошибок пробрасываем дальше throw err > logError(err)>>// parse-module.js // Есть свой тип ошибки class ParsingError extends Error . > function parse(data) try parseData(data) > catch (err) if (err.name !== 'ParsingError') // Другой тип ошибок пробрасываем дальше throw err > logError(err) > >
Таким образом, можно разделить ответственность, а обработкой проброшенной ошибки займётся внешний catch.
import parse from 'parse-module' try parse(data)> catch (e) console.log('Неизвестная ошибка парсинга:', e)>import parse from 'parse-module' try parse(data) > catch (e) console.log('Неизвестная ошибка парсинга:', e) >
Ошибки в асинхронном коде
Скопировать ссылку "Ошибки в асинхронном коде" Скопировано
Конструкция try . . . catch работает только синхронно. Таким образом, с помощью try . . . catch нельзя обработать ошибку, которая возникла в асинхронном коде.
try // Код выполнится корректно, т.к. отсюда вернулся промис Promise.reject('err')> catch (e) // Ошибка не будет поймана console.log('Ошибка', e)> try // Здесь также код выполнится корректно, потому что установил таймаут без ошибок setTimeout(() => throw Error('ошибка') >, 1000)> catch (e) // Ошибка из таймаута также сюда не попадёт console.log('Ошибка', e)>try // Код выполнится корректно, т.к. отсюда вернулся промис Promise.reject('err') > catch (e) // Ошибка не будет поймана console.log('Ошибка', e) > try // Здесь также код выполнится корректно, потому что установил таймаут без ошибок setTimeout(() => throw Error('ошибка') >, 1000) > catch (e) // Ошибка из таймаута также сюда не попадёт console.log('Ошибка', e) >
Однако, если записать асинхронный код в синхронном стиле с помощью async/await, то в этом случае обработку ошибок можно осуществлять с помощью try . . . catch .
async function handlePromise() try // Промис вернется с ошибкой await Promise.reject('err') > catch (e) // Теперь ошибка будет поймана console.log('Ошибка', e) // err >> handlePromise()async function handlePromise() try // Промис вернется с ошибкой await Promise.reject('err') > catch (e) // Теперь ошибка будет поймана console.log('Ошибка', e) // err > > handlePromise()
Чтобы поймать ошибку из set Timeout ( ) , блоки try . . . catch должны находиться внутри функции.
На практике
Скопировать ссылку "На практике" Скопировано
Егор Огарков советует
Скопировать ссылку "Егор Огарков советует" Скопировано
Любой асинхронный код можно переписать в синхронном стиле через async / await , чтобы использовать единый стиль обработки ошибок, используя try . . . catch . Например, перепишем установку таймаута из примера выше:
function wait(ms) return new Promise((resolve) => setTimeout(resolve, ms))> async function timeout(fn, ms) try // Ждем таймаут await wait(ms) // И выполняем функцию fn() > catch (err) // Ловим ошибку console.log('Ошибка', err) >>function wait(ms) return new Promise((resolve) => setTimeout(resolve, ms)) > async function timeout(fn, ms) try // Ждем таймаут await wait(ms) // И выполняем функцию fn() > catch (err) // Ловим ошибку console.log('Ошибка', err) > >
Теперь можно вызывать функцию как прежде, ошибка будет поймана.
timeout(() => throw Error('ошибка')>, 1000)timeout(() => throw Error('ошибка') >, 1000)
Исключения — JS: Введение в ООП
Отдельная большая тема в программировании – обработка ошибок. До сих пор нам удавалось избегать её, но в реальном мире, где приложения содержат тысячи, десятки и сотни тысяч (а то и все миллионы) строк кода, обработка ошибок влияет на многое: простоту модификации и расширения, адекватное поведение программы для пользователя в разных ситуациях.
В этом уроке мы рассмотрим механизм исключений. Но перед тем, как изучать новые конструкции, поговорим про ошибки вообще.
В JavaScript у строк есть метод, который называется text.indexOf(str) . Он ищет подстроку str внутри текста text и возвращает индекс начала этой подстроки в тексте. Что произойдёт, если подстрока не была найдена? Является ли это поведение ошибкой? Нет. Это штатное поведение функции. От того, что подстрока не была найдена, ничего страшного не случилось. Представьте себе любой редактор текста и механизм поиска внутри него. Ситуация, когда ничего не было найдено, возникает постоянно, и это не ломает работу программы.
Кстати, посмотрите в документацию этой функции, каким образом она говорит о том, что подстрока не была найдена?
Другая ситуация. В тех же редакторах есть функция "открыть файл". Представьте, что во время открытия файла что-то пошло не так, например, его удалили. А это ошибка или нет? Да, в этой ситуации произошла ошибка, но это не ошибка программирования. Подобная ошибка может возникнуть всегда, независимо от желания программиста. Он не может избежать её появления. Единственное, что он может — правильно реализовать её обработку.
Ещё один интересный вопрос, насколько это критичная ошибка? Должна ли она приводить к остановке всего приложения или нет? В плохо написанных приложениях, там где неправильно реализована обработка ошибок, такая ситуация приведёт к краху всего приложения, и оно завершится. В хорошо написанном приложении не произойдёт ничего страшного. Пользователь увидит предупреждение о том, что файл не читается, и сможет выбрать дальнейшие действия, например, попытаться прочитать его снова или выполнить другое действие.
Сказанное выше имеет очень серьёзные следствия. Одна и та же ситуация на разных уровнях может как являться ошибкой, так и быть вполне штатной ситуацией. Например, если задача функции читать файл, а она не смогла этого сделать, то с точки зрения этой функции произошла ошибка. Должна ли она приводить к остановке всего приложения? Как мы выяснили выше – не должна. Принимать решение о том, насколько критична данная ситуация, может приложение, которое использует эту функцию, но не сама функция.
Коды возврата
В языках, появившихся до 1990 года (примерно), обработка ошибок выполнялась через механизм возврата функцией специального значения. Например, в Си, если функция не может выполнить свою задачу, то она должна вернуть специальное значение, либо NULL , либо отрицательное число. Значение этого числа говорит о том, какая ошибка произошла. Например:
int write_log() int ret = 0; // return value 0 if success FILE *f = fopen("logfile.txt", "w+"); // Проверяем, получилось ли открыть файл if (!f) return -1; // Проверяем, что не достигли конца файла if (fputs("hello logfile!", f) != EOF) // continue using the file resource > else // Файл закончился ret = -2; > // Не получилось закрыть файл if (fclose(f) == EOF) ret = -3; return ret; >
Обратите внимание на условные конструкции и постоянное присваивание переменной ret . Фактически каждая потенциально опасная операция должна проверяться на успешность выполнения. Если что-то пошло не так, то функция возвращает специальный код.
И вот тут начинаются проблемы. Как показывает жизнь, в большинстве ситуаций ошибка обрабатывается не там, где она возникла, и даже не уровнем выше. Предположим, что есть функция A, которая вызывает код, потенциально приводящий к ошибке, и она его должна уметь правильно обработать и сообщить пользователю о проблеме. При этом сама ошибка происходит внутри функции E, которая вызывается внутри A не напрямую, а через цепочку функций: A => B => C => D => E. Подумайте, к чему приводит такая схема? Все функции в этой цепочке, даже несмотря на то, что они не обрабатывают ошибку, обязаны знать про неё, отлавливать её и так же возвращать наружу код этой ошибки. В итоге кода, который занимается ошибками, становится так много, что за ним теряется код, выполняющий исходную задачу.
Стоит сказать, что существуют схемы обработки ошибок, которые не обладают такими недостатками, но работают по принципу возврата. Например, монада Either.
Исключения
Именно на этом фоне и возник механизм исключений. Его главная цель — передать ошибку из того места, где она возникла, в то место, где её можно обработать, минуя все промежуточные уровни. Другими словами, механизм исключений раскручивает стек вызовов самостоятельно.
С исключениями нужно запомнить две вещи: код, в котором произошла ошибка, выбрасывает исключение, а код, в котором ошибка обрабатывается – его ловит.
// Функция, которая может выбросить исключение const readFile = (filepath) => if (!isFileReadable(filepath)) // throw – способ выбросить исключение throw new Error(`'$filepath>' is not readable`); > // . >; // Где-то в другом месте программы const run = (filepath) => try // Функция, которая вызывает readFile. Возможно не напрямую, а через другие функции. // Для механизма исключений это не важно. readFile(filepath); > catch (e) // Этот блок выполняется только в одном случае, если в блоке try было выброшено исключение // Любая обработка ошибки, например, вывод в консоль console.log(e); > // Если тут будет код, он продолжит выполняться >;
Сами исключения – это объекты Error . Эти объекты содержат внутри себя сообщение, переданное в конструктор, трассировку стека и другие полезные данные.
Самостоятельно исключение выбрасывается с помощью ключевого слова throw :
const e = new Error('Тут любой текст'); throw e; // Исключение можно создать отдельно, а можно сразу же там, где используется throw
Можно выбрасывать не только исключения, но и любые выражения:
const message = 'Тут любой текст'; throw message;
throw прерывает дальнейшее выполнение кода. В этом смысле оно подобно return , но в отличие от него, прерывает выполнение не только текущей функции, но и всего кода, вплоть до ближайшего в стеке вызовов блока catch .
Конструкция try catch - это специальная инструкция из двух блоков, которая позволяет перехватить все исключения и их обработать.
Первый блок формируется после try :
Любые исключения, которые будут выброшены кодом, расположенным внутри этого блока, будут перехвачены и переданы во второй блок. Если ошибки не было, то этот блок пропускается.
catch (e) // . >
Внутри этого блока будет доступна ошибка в переменной e . Можно задать любое имя переменной:
try // . > catch (myError) console.log(myError); >
Внутри блока catch можно выполнять любой код, даже выбрасывать новые исключения, которые могут перехватываться блоком try catch уровнем выше:
const myFunc = () => try // . > catch (e) throw new Error('new error'); > >; try myFunc(); > catch (e) console.log(e); // => new error >
Открыть доступ
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
- 130 курсов, 2000+ часов теории
- 1000 практических заданий в браузере
- 360 000 студентов
Наши выпускники работают в компаниях: