В практически любой
язык программирования, в том числе и JavaScript, имеет
специальную конструкцию:
try/catch/finally
позволяющую
«ловить» ошибки во время выполнения программы и предпринимать определенные
действия (обрабатывать эти ошибки). Общая идея работы этого блока заключается в
том, что мы помещаем в блок try потенциально уязвимый скрипт:
let res = 0;
try {
res = 5/d;
console.log(res);
}
catch(error) {
console.log(error);
}
И, если в нем
что-то пошло не так, то выполнение программы прерывается и управление
передается блоку catch, в котором можно реализовать
определенные действия по обработке ошибки, например, вывести сообщение в
консоль.
Причем, обратите
внимание, если бы в нашем примере отсутствовал блок try, то скрипт
полностью прекратил бы свою работу и его выполнение было бы остановлено
браузером. Но, благодаря наличию этого блока, прерывается только та часть
программы, которая находится в try и, далее, мы уже попадаем в блок catch, чтобы
выполнить определенные действия при нештатной ситуации. И это значительно
повышает надежность работы скрипта.
Если же в блоке try ошибок не
возникло, то блок catch пропускается и не выполняется:
let res = 0;
try {
let d = 2;
res = 5/d;
console.log(res);
}
catch(error) {
console.log(error);
}
Но, обратите
внимание, блок try выполняется в синхронном режиме, то есть, например,
если в него поместить вот такой асинхронный вызов функции:
let res = 0;
try {
setTimeout(function() {
res = 5/d;
console.log(res);
}, 0);
}
catch(error) {
console.log(error);
}
То ошибка
обработана не будет, т.к. анонимная функция внутри setTimeout выполняется уже
после выполнения блока try. То есть, блок try сначала
полностью выполнится, а затем, будет запущена анонимная функция. В такой
ситуации блок try/catch следует
поместить внутрь анонимной функции:
let res = 0;
setTimeout(function() {
try {
res = 5/d;
console.log(res);
}
catch(error) {
console.log(error);
}
}, 0);
Свойства объекта ошибки
В языке JavaScript объект ошибки
блока catch имеет три
полезных атрибута:
-
name
– содержит
название ошибки;
-
message
– содержит
текст ошибки;
-
stack – стек вызова
до возникновения ошибки (нестандартное свойство).
Например, мы
можем вывести эти атрибуты в консоль, следующим образом:
let res = 0;
try {
d = 2;
res = 5/d;
console.log(res);
}
catch(error) {
console.log(error.name);
console.log(error.message);
console.log(error.stack);
}
Блок finally
При обработке
ошибок часто используется еще один необязательный блок finally, который
выполняется в любом случае: возникли ошибки или нет, причем, в последнюю
очередь. Зачем он может понадобиться? Например, чтобы выполнить некоторые
обязательные действия после завершения соединения с сервером:
Мы здесь
определяем некоторый флаг (flSend) для определения выполнения
текущего запроса и если он установлен в true, то запрос
второй раз отправляться не будет. Соответственно, потом, после успешной или
ошибочной обработки, этот флаг обязательно нужно вернуть в значение false. Как раз это и
делается в блоке finally.
Генерация собственных исключений – throw
JavaScript позволяет
генерировать свои собственные исключения с помощью оператора
throw
<объект
ошибки>
В качестве
объекта ошибки может быть и число и строка или еще какой-либо примитив и
объект, но лучше использовать стандартные классы ошибок, например:
-
Error
– для
общих ошибок;
-
SyntaxError
– для синтаксических ошибок;
-
TypeError
– для ошибок типов данных;
-
ReferenceError – ссылка на
несуществующую переменную
и так далее. Чтобы
воспользоваться этими классами, достаточно создать соответствующий объект:
let err1 = new Error("Ошибка выполнения");
let err2 = new SyntaxError("Ошибка синтаксиса");
let err3 = new TypeError("Ошибка типов данных");
и указать после
оператора throw:
throw err1;
throw new SyntaxError("Ошибка синтаксиса");
Например,
зададим функцию деления двух чисел, которая генерирует исключение, если
знаменатель равен нулю:
function divide(a, b) {
if( b == 0 ) {
throw new Error("Деление на ноль");
}
return a/b;
}
И, далее,
вызываем ее в блоке try:
let res = 0;
try {
res = divide(1, 0);
console.log(res);
}
catch(error) {
console.log(error.name);
console.log(error.message);
}
В консоли увидим
строчки:
Error, Деление на
ноль
То есть,
свойство name ссылается на
имя конструктора объекта ошибки, а message – на аргумент
этого объекта.
Проброс исключений
В нашей
последней реализации блок catch отлавливает все исключения в блоке try. Но, что если
мы хотим отслеживать лишь определенный их тип, например, Error, а остальные не
трогать – пересылать дальше – блокам try более высокого уровня:
Это и называется
пробросом исключения. Для его реализации нам достаточно проверить во внутреннем
блоке catch тип ошибки и,
если она не соответствует Error, то передать дальше:
// let res = 0;
try {
res = divide(1, 2);
console.log(res);
}
catch(error) {
if(error.name == "Error") {
console.log(error.name);
console.log(error.message);
}
else {
throw error;
}
}
Соответственно,
внутри будет обрабатывать только исключения Error, а остальные
пробрасывать блокам более высокого уровня.