Перехват ошибок в JavaScript, «try..catch»

Перехват ошибок в JavaScript, «try..catch»

Здравствуйте!  В этом уроке я хотел бы рассказать об ошибках в JavaScript и о том как их собственно обрабатывать.  Ведь часто случается, что ошибки  происходят  время от времени  и тут даже дело не в наличие опыта программирования или даже  полном его отсутствии. Ведь и у матерых программистов тоже случаются ошибки никто от этого не застрахован.

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

А вот логические ошибки с ними все не так просто потому, что они приводят к неправильному выполнению кода программы. Поэтому для их устранения потребуется отладка программы, чтобы понять что собственно происходит на каждом шаге скрипта. Мы же с вами  здесь рассмотрим в основном локализацию синтаксических ошибок с помощью конструкции try…catch.
перехват ошибок в javascript try...catch

Конструкция перехвата ошибок try…catch

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

try {

  // код ...

} catch (err) {

  // обработка ошибки

}

Работает эта конструкция таким образом:

  1. Выполняется код внутри блока try, так называемой ловушки.
  2. Если в нём не встречаются ошибки, то блок catch(err) игнорируется.
  3. А вот, если в нём возникнет ошибка, то выполнение try будет прервано на ошибке, и управление  передается в начало блока catch(err). При этом переменная err (можно выбрать любое другое название) будет содержать объект ошибки с подробнейшей информацией о произошедшей ошибке.

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

Рассмотрим это на примерах.

  • Пример без ошибок: при запуске сработают alert (1) и (2):
    try { alert('Блок try'); // (1) <-- // .. код без ошибок alert('Конец try'); // (2) <-- } 
    catch(e) { alert('Блок catch не получит управление, так как нет ошибок'); // (3) } 
    alert("Потом код продолжит выполнение...");

А вот пример с ошибкой: при запуске сработают (1) и (3):

try { alert('Начало try'); // (1) <-- lalala; // ошибка, переменная не определена! alert('Конец блока try'); // (2) } 
catch(e) { alert('Ошибка ' + e.name + ":" + e.message + "\n" + e.stack); // (3) <-- } 
alert("Потом код продолжит выполнение...");

В случае, если грубо нарушена структура кода, не закрыта фигурная скобка или где-то стоит лишняя запятая, то вам никакой try..catch не поможет. Это синтаксические ошибки, интерпретатор такой код просто не понимает.

Читайте также  Рекурсивные функции

Важное замечание try..catch может работать только в синхронном коде

Ошибку, которая может произойти в коде,который будет выполняться через время например в setTimeout, try..catch не поймает:

try {
  setTimeout(function() {
    throw new Error(); // вылетит в консоль
  }, 1000);
} catch (e) {
  alert( "не сработает" );
}

На момент запуска функции, назначенной через setTimeout, этот код уже завершится, и интерпретатор выйдет из блока try..catch.

Для того чтобы Чтобы поймать ошибку внутри функции из setTimeout, и try..catch должен быть в той же функции.

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

В примере выше мы видим объект ошибки, который вы передаете в блок catch.  У него есть три основных свойства:

name
Тип ошибки. Например, при обращении к переменной, которой нет: «ReferenceError».
message
Сообщение о деталях ошибки
stack
Выводит более полную информацию, с указанием строки, где произошла ошибка и саму ошибку.

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

Оператор throw

Данный оператор throw генерирует ошибку.

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

Технически в качестве объекта ошибки вы можете передать что угодно, это может быть даже  и не объект, а число или строка.

В качестве конструктора ошибок  вы можете использовать встроенный конструктор: new Error(message) или любой другой.

В JavaScript также встроен ряд конструкторов для стандартных ошибок: SyntaxError, ReferenceError, RangeError и некоторые другие.

В данном примере мы используем конструктор new SyntaxError(message). Он создаёт ошибку того же типа, что и JSON.parse.

var data1 = '{ "age": 30 }'; // данные неполны
try {
  var user1 = JSON.parse(data); // <-- выполнится без ошибок
  if (!user1.name) {
    throw new SyntaxError("Данные некорректны");
  }
  alert( user1.name );

} catch (e) {
  alert( "Извините, в данных ошибка" );
}

Получается, что блок catch – единственное место для обработки ошибок во всех случаях.

Оборачивание исключений

И, для полноты картины – последняя,  техника по работе с ошибками. Она, является стандартной практикой во многих языках программирования.

Цель функции readData в примере выше – это прочитать данные. При этом чтении могут возникать различные ошибки, не только SyntaxError, но и, возможно, к примеру URIError  и другие.

Код, который вызвает 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 {
  alert( 'try' );
  if (confirm('Сгенерировать ошибку?')) BAD_CODE();
} catch (e) {
  alert( 'catch' );
} finally {
  alert( 'finally' );
}

У него 2 варианта работы:

  1. Если вы ответите на вопрос «сгенерировать ошибку?» утвердительно, то try -> catch -> finally.
  2. Если ответите отрицательно, то try -> finally.

Секцию finally используют, чтобы завершить начатые операции при любом варианте развития событий.

Последняя надежда: window.onerror

Допустим, ошибка произошла вне блока try..catch или выпала из try..catch наружу, во внешний код. Скрипт перестал работать.

Можно ли как-то узнать о том, что произошло? Да можно.

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

Читайте также  Таймеры в JavaScript

Необходимо лишь позаботиться, чтобы функция была назначена заранее.

Например:

<script>
  window.onerror = function(message, url, lineNumber) {
    alert("Поймана ошибка, выпавшая в глобальную область!\n" +
      "Сообщение: " + message + "\n(" + url + ":" + lineNumber + ")");
  };

  function readData() {
    error(); // ой, что-то не так
  }

  readData();
</script>

Как правило, роль window.onerror заключается не в том, чтобы оживить скрипт – скорее всего, это уже невозможно, а в том, чтобы отослать сообщение об ошибке на сервер, где разработчики о ней узнают.
Существуют даже специальные веб-сервисы, которые предоставляют скрипты для отлова и аналитики таких ошибок, например: https://errorception.com/ и http://www.muscula.com/.

Итого

Обработка ошибок –  это очень полезная и важная тема.

В JavaScript для этого используют следующие методы:

  • Конструкция try..catch..finally –  позволяет обработать ошибку в скрипте, как бы локализовать область с ошибкой.
      try { .. попытка выполнить код .. } catch(e) { .. перехват исключения .. } finally { .. выполняется всегда .. }
  • Возможны также  и другие варианты try..catch или try..finally.
  • Для  генерации пользовательской  ошибки используется оператор throw err.

Есть также и другие интересные приемы:

  • Проброс исключения – catch(err) обрабатывает только те ошибки, которые вы хотели бы  в нём увидеть,  а вот остальные – пробрасывать дальше через  throw err. Определить, нужная ли это ошибка, можно, по свойству name.
  • Оборачивание исключений –  эта некая функция, которая позволяет обернуть исключения  и пробросить их дальше.
  • В window.onerror можно присвоить специальную  функцию, которая выполнится при любой ошибке.

Задача

Сравните два фрагмента кода.

  1. Первый использует finally для выполнения кода по выходу из try..catch:
    try {
      начать работу
      работать
    } catch (e) {
      обработать ошибку
    } finally {
      финализация: завершить работу
    }

Второй фрагмент просто ставит очистку ресурсов за try..catch:

try {
  начать работу
} catch (e) {
  обработать ошибку
}

финализация: завершить работу

Нужно, чтобы код финализации всегда выполнялся при выходе из блока try..catch и, таким образом, заканчивал начатую работу. Имеет ли здесь finally какое-то преимущество или оба фрагмента работают одинаково?

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

Плюсануть
Поделиться

Об авторе

admin administrator

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: