Блоки try/catch/finally, оператор throw, проброс исключений

В практически любой язык программирования, в том числе и 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, а остальные пробрасывать блокам более высокого уровня.