Мастер-классы по Javascript Екатеринбург Ростов-на-Дону Москва Узнать больше...
Содержание (скрыть) Содержание (показать)

Исключения

  1. Перехват ошибок в коде
    1. Конструкция try..catch
    2. Объект ошибки
    3. Секция finally
  2. Генерация своих ошибок
    1. Пример: проверка значений
    2. Генерация ошибки: throw
    3. Вложенные вызовы и try..catch
  3. Итого

Исключения — мощное встроенное средство по обработке ошибок и управлению потоком выполнения.

Перехват ошибок в коде

Ошибки в коде бывают двух типов.

  1. Синтаксические — когда нарушена структура кода, например:
    for (a = 5) { // неправильный аргумент for
    
    ]; // неправильная закрывающая скобка
    
    Такой код браузер не может разобрать и выполнить.

    Их также называют «ошибки времени компиляции», поскольку они происходят на этапе разбора кода.

  2. Семантические, также называемые «ошибки времени выполнения» — возникают, когда браузер смог прочитать скрипт и уже выполняет код, но вдруг натыкается на проблему.

    Например, «не определена переменная»:

    alert(nonexistant); // переменная не определена.
    

Стандартный метод контроля ошибок — предварительная проверка. Мы можем посмотреть, существует ли переменная перед обращением к ней.

Но предварительная проверка возможна не всегда.

Например, если в документе есть IFRAME, то к его данным можно обратиться, но только если в нём открыт документ с того же домена. Иначе будет ошибка.

Эта мера направлена на защиту посетителей, чтобы страница со «злого сайта» не могла открыть в себе IFRAME, скажем, с почтой и, после того, как посетитель войдёт, прочитать из него все письма.

Представим, что у нас есть IFRAME и мы предполагаем, что можем в него залезть, но не уверены. Ограничение безопасности браузера сработает даже в том случае, если мы попробуем прочитать его URL — будет ошибка.

Пример (не выведет location из-за ошибки):

<iframe src="http://example.com" style="height:60px"></iframe>

<script>
onload = function() { // запустится при полной загрузке
*!*
  alert(frames[0].location); // будет ошибка
*/!*
}
</script>

Вполне реальна ситуация, когда есть IFRAME, и мы хотим получить информацию из него, но, возможно, посетитель перешел в нём на другой домен. Тогда при попытке получить данные возникнет ошибка и скрипт «рухнет».

Как сделать, чтобы скрипт смог получить информацию об ошибке и продолжить работу?

…Как раз здесь нам поможет try..catch!

Конструкция try..catch

Конструкция try..catch состоит из двух основных блоков: try, и затем catch. Например:

try {

  alert(frames[0].location);

  ...

  alert('Выполнено без ошибок!');

} catch(e) {

  alert('Ошибка!');

}

Работает это так:

  1. Выполняется код внутри блока try.
  2. Если в нём возникнет ошибка, то выполнение try прерывается, и управление прыгает в начало блока catch.

    Например:

    try {
    
      alert('Начало блока try');  // *!*(1) <--*/!*
    
    *!*
      lalala; // ошибка, переменная не определена!
    */!*
    
      alert('Конец блока try');  
     
    } catch(e) {
    
      alert('Управление получил блок catch!'); // *!*(2) <--*/!*
    
    }
    

    Будут два alert'а: (1) и (2).

  3. Если ошибки нет — блок catch игнорируется.

    Например:

    try {
    
      alert('Начало блока try');  // *!*(1) <--*/!*
    
      // .. код без ошибок
    
      alert('Конец блока try');   // *!*(2) <--*/!*
    
    } catch(e) {
    
      alert('Блок catch не получит управление');
    
    }
    

    Будут два alert'а: (1) и (2). Блок catch не будет использован.

Логика работы try..catch позволяет отловить любые семантические ошибки.

В том числе, обработать ошибку при чтении из IFRAME'а:

<iframe src="http://example.com" style="height:60px"></iframe>

<script>
onload = function() { 
*!*
  try {

    alert(frames[0].location); 

  } catch(exception) {

    alert('Ой, а ифрейм-то на другом домене...');

  }
*/!*
}
</script>

Объект ошибки

У блока catch есть аргумент, в примере выше он обозначен exception. Это — объект ошибки или, как ещё говорят, объект исключения (exception object).

Он содержит информацию о том, что произошло и может быть разным, в зависимости от ошибки.

Как правило, это объект встроенного типа Error и производных от него.

Есть несколько свойств, которые присутствуют в объекте ошибки во всех браузерах:

name
Тип ошибки. Например, при обращении к несуществующей переменной равен "ReferenceError".
message
Текстовое сообщение о деталях ошибки.

В зависимости от браузера, у него могут быть и дополнительные свойства, см. например Error в MDN и Error в MSDN.

В примере ниже идёт попытка вызова числовой переменной как функции. Это ошибка типа name = "TypeError", с сообщением message, которое зависит от браузера:

try {
  var a = 5;

  var res = a(1); // ошибка!

} catch(e) {
  alert("name:" + e.name + "\nmessage:" + e.message);
}

Секция finally

Конструкция try..catch может содержать ещё один блок: finally. Выглядит этот расширенный синтаксис так:

*!*try*/!* {
   .. пробуем выполнить код ..
} *!*catch*/!*(e) {
   .. перехватываем исключение ..
} *!*finally*/!* {
   .. выполняем всегда ..
}

Секция finally не обязательна, но если она есть, то она выполняется всегда:

  • после блока try, если ошибок не было,
  • после catch, если они были.

Её используют, чтобы завершить начатые операции и очистить ресурсы, которые должны быть очищены в любом случае — как при ошибке, так и при нормальном потоке выполнения.

var tmpObject = ... // создать что-то, что нужно будет очистить

try {
  .. поработать с tmpObject ..
} catch(e) {
  .. обработать ошибку ..
} finally {
  .. // очистить tmpObject 
}

finally и return

Секция finally срабатывает при любом выходе из try..catch, в том числе и return.

В примере ниже, из try происходит return, но finally получает управление до того, как контроль возвращается во внешний код.

function func() {
  
  try {
    // сразу вернуть значение
    return 1;

  } catch(e) {
    alert('Сюда управление не попадёт, ошибок нет');
  } finally {
*!*
    alert('Вызов завершён');
*/!*
  }
}

alert( func() );

Это гарантирует освобождение ресурсов в любом случае.

Возможна также форма try..finally, без catch:

// редко используется, но тоже можно
try {
  ..
} finally {
  ..
}

Генерация своих ошибок

Перед тем, как перейти к генерации ошибок, начнём с небольшого примера, который позволит понять, зачем это нужно.

Пример: проверка значений

Ошибки могут быть частью нормального потока выполнения скрипта.

Например, они могут возникать в процессе проверки значений формы. Функция получает то, что ввёл посетитель и, в простейшем случае, возвращает true/false:

function checkValidAge(age) {
  if (age >= 18) {
    return true; // OK
  } else {
    return false; // Ошибка
  }
}

..Но в случае обработки данных из формы простого true/false может быть недостаточно! Если проверка достаточно сложная, то хорошо бы вернуть ошибку, с текстовым описанием, что именно не так.

Решение без исключений (плохое!) выглядит так:

// функция возвращает:
//   false, если всё хорошо
//   или строку с ошибкой
function checkValidAge(age) {
  if (age >= 18) {
    return false;
  } else {
    return "Вы слишком молоды. Приходите через " + (18-age) + " лет";
  }
}

..То есть, возврат false используется в смысле «всё в порядке», а строка означает ошибку. Это позволяет использовать функцию так:

var error = checkValidAge(age);
if (error) {
  // показать ошибку
}

Почему это решение плохое?

В данном случае оно работает, и даже может показаться симпатичным, но что, если строка является нормальным результатом функции? Как тогда обозначить ошибку? Вернуть специальный объект?..

Зачем придумывать? Ответ уже есть — и это исключения! Генерация своей ошибки предоставляет альтернативный подход к проблеме, который мы сейчас рассмотрим.

Генерация ошибки: throw

Синтаксис: throw <объект ошибки>.

Инициирует ошибку, которую можно поймать в catch. Объектом ошибки может быть что угодно, например число:

try { 
  throw 123;
} catch(e) {
  alert(e); // 123
}

Обычно, всё же, используют более информативные объекты, как минимум с теми же свойствами name, message, что и у встроенного Error.

Перепишем функции, которые осуществляют проверки, с использованием throw:

function BadValueError(name, message) {
  this.name = "BadValue";
  this.message = message;
}

function checkValidAge(age) {
  if (age < 18) {
    throw new BadValueError("Возраст не подходит");
  }
}

function checkRequired(value) {
  if (value == '') {
    throw new BadValueError("Отсутствует значение");
  }
}

Код для проверки:

var value = ageInput.value;

try {
  checkRequired(value);
  checkInteger(value);
  checkValidAge(value);

  /* ввод успешен */
} catch(e) {
  /* обработать ошибку */
}

Гораздо короче, не правда ли? И при этом надёжнее, т.к. try..catch поймают любую ошибку, даже если посетитель ввёл что-то, от чего в функции возникла не предусмотренная программная ошибка.

Вложенные вызовы и try..catch

Брошенное исключение выпадает из всех циклов, функций и т.п.

Это — особое, уникальное свойство исключений.

Оно означает, что в случае вложенных вызовов — не важно, где было исключение, оно будет поймано внешней функцией.

Например:

function checkAll(value) {
  checkRequired(value);
  checkInteger(value);
  checkValidAge(value);
}


try {
  checkAll(value);

  alert('Да, вы нам подходите!');
 
} catch(e) {
*!*
  if (e.name == "BadValue") {
    // ок, я знаю что делать с этой ошибкой
    alert('Ошибка '+e.message);
  } else {
    // ошибка неизвестна, пробросить ее дальше
    throw e;
*/!*
  }
}

В примере выше дополнительно использована техника «проверки и проброса» исключения. Её смысл в том, что исключения могут быть самыми разными. Возможно, это ошибка проверки, а может быть — нет прав на действие, а может быть — доступ к необъявленной переменной… Возможно что угодно.

Функция checkAll умеет обрабатывать только ошибку проверки BadValue, что и делает. В том случае, если исключение какое-то другое, то она «пробрасывает» (throw e) его дальше, так что оно либо попадёт во внешнюю функцию, которая знает, что с ним делать, либо выпадает из скрипта как ошибка — и тогда разработчик увидит его в консоли.

try..finally

Синтаксис try..finally (без catch) применяется тогда, когда мы хотим произвести очистку в finally, но совсем не умеем обрабатывать ошибки.

Итого

Исключения в JavaScript нужны редко. Но там, где они нужны — они нужны.

  • Основная область применения — попытка сделать что-то, что может вернуть ошибку, но проверить заранее нельзя. Например, получить информацию из IFRAME'а.
  • Другая область применения — завернуть блок кода, в котором может быть ошибка, в конструкцию try..catch, и вместо многочисленных проверок обработать ошибку один раз, внизу.

    Мы видели это в примере с checkRequired/checkInteger/checkAge.

Ошибка, генерируемая throw, «выпадает» из функции, и передаёт управление на ближайший catch:

function a() {  b(); }

function b() {  c(); }

function c() { throw new Error("Ошибка!"); }

*!*
try {
  a(); // a() -> вызывает b() -> вызывает c() -> Ошибка!
} catch(e) { 
  alert(e); // <-- ошибка выпадет сюда 
}
*/!*

Это делает try..catch + throw мощным средством контроля выполнения.

При использовании своих ошибок рекомендуется генерировать объекты встроенного типа Error или, когда мы познакомимся с наследованием в JavaScript — наследующие от него.

Ваши объекты также могут содержать дополнительную информацию об обстоятельствах ошибки, полезную для её обработки:

var err = new Error("Ошибка");
*!*
err.extra = new Date();
*/!*

alert(err.message + " в " + err.extra); // "Ошибка в (дата)"

Напишите интерфейс, который принимает математическое выражение (prompt) в и возвращает его результат.

Интерфейс может допускать использование любых функций.

При ошибке нужно выводить сообщение и просить переввести выражение. Ошибкой считается некорректное выражение, такое как 2+ и выражение, возвращающее NaN, например 0/0.

Демо в новом окне: tutorial/intro/eval-calc-try.html.

Схема решения
Решение
Схема решения

Вычислить любое выражение нам поможет eval:

alert( eval("2+2") ); // 4

Считываем выражение в цикле while(true). Если при вычислении возникает ошибка — ловим её в try..catch.

Ошибкой считается, в том числе, получение NaN из eval, хотя при этом исключение не возникает. Можно бросить своё исключение в этом случае.

Код решения
Код решения

while(true) {
  expr = prompt("Введите выражение?", '2-');

  try { 
    res = eval(expr); // при ошибке будет catch

    if (isNaN(res)) { // наша ошибка
      throw new Error("Результат неопределён");
    }

    break; // все ок, выход из цикла

  } catch(e) {

    alert("Ошибка: "+e.message+", повторите ввод");

  }
}

alert(res);

Полное решение: tutorial/intro/eval-calc-try.html.


Комментарии

  1. Приветствуются комментарии, содержащие дополнения и вопросы по статье, и ответы на них.
  2. Если ваш комментарий касается задачи -- откройте её в отдельном окне и напишите там.
  3. Комментарии без смысла, с рекламой или не о статье вообще - удаляются.
Наверх

Содержание

Реклама

Нашли опечатку?

Нашли опечатку на сайте? Что-то кажется странным?
Выделите соответствующий текст и нажмите Ctrl+Enter!

Последние Комментарии

Помоги другим!

Помоги другим узнать о хорошей статье!