Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Язык JavaScript часть1.pdf
Скачиваний:
190
Добавлен:
22.03.2016
Размер:
8.92 Mб
Скачать

Проверять выражение на корректность не требуется.

Запустить демо

К решению

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

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

Обычно скрипт при ошибке, как говорят, «падает», с выводом ошибки в консоль.

Но бывают случаи, когда нам хотелось бы как-то контролировать ситуацию, чтобы скрипт не просто «упал», а сделал что-то разумное.

Для этого в JavaScript есть замечательная конструкция try..catch.

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

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

try {

//код ...

}catch (err) {

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

}

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

1.Выполняется код внутри блока try.

2.Если в нём ошибок нет, то блок catch(err)игнорируется, то есть выполнение доходит до конца tryи потом прыгает через catch.

3.Если в нём возникнет ошибка, то выполнение tryна ней прерывается, и управление прыгает в начало блока catch(err).

При этом переменная err(можно выбрать и другое название) будет содержать объект ошибки с подробной информацией о произошедшем.

Таким образом, при ошибке в tryскрипт не «падает», и мы получаем возможность обработать ошибку внутри catch.

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

Пример без ошибок: при запуске сработают alert(1)и (2):

try {

alert('Начало блока try'); // .. код без ошибок alert('Конец блока try');

//(1) <

//(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здесь не поможет. Такие ошибки

называются синтаксическими, интерпретатор не может понять такой код.

Здесь же мы рассматриваем ошибки семантические, то есть происходящие в корректном коде, в процессе выполнения.

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

Ошибку, которая произойдёт в коде, запланированном «на будущее», например, в setTimeout, try..catchне поймает:

try { setTimeout(function() {

throw new Error(); // вылетит в консоль }, 1000);

}catch (e) {

alert( "не сработает" );

}

На момент запуска функции, назначенной через 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 = '{"name":"Вася", "age": 30}'; // строка с данными, полученная с сервера

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); // < ошибка при выполнении alert( user.name ); // не сработает

}catch (e) {

// ...выполнится catch

alert( "Извините, в данных ошибка, мы попробуем получить их ещё раз" ); alert( e.name );

alert( e.message );

}

Здесь в alertтолько выводится сообщение, но область применения гораздо шире: можно

повторять запрос, можно предлагать посетителю использовать альтернативный способ, можно отсылать информацию об ошибке на сервер… Свобода действий.

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

Представим на минуту, что данные являются корректным JSON… Но в этом объекте нет нужного свойства name:

var data = '{ "age": 30 }'; // данные неполны

try {

var user = JSON.parse(data); // < выполнится без ошибок alert( user.name ); // undefined

}catch (e) {

// не выполнится

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

}

Вызов JSON.parseвыполнится без ошибок, но ошибка в данных есть. И, так как свойство name обязательно должно быть, то для нас это такие же некорректные данные как и "Has Error".

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

Оператор 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); // < выполнится без ошибок

if (!user.name) {

throw new SyntaxError("Данные некорректны");

}

alert( user.name );

}catch (e) {

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

}

Получилось, что блок 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 {

alert( 'try' );

if (confirm('Сгенерировать ошибку?')) BAD_CODE();

}catch (e) { alert( 'catch' );

}finally {

alert( 'finally' );

}

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

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

2.Если ответите отрицательно, то try > finally.

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

Например, мы хотим подсчитать время на выполнение функции sum(n), которая должна возвратить сумму чисел от 1до nи работает рекурсивно:

function sum(n) {

return n ? (n + sum(n 1)) : 0;

}

var n = +prompt('Введите n?', 100);

var start = new Date();

try {

var result = sum(n);

}catch (e) { result = 0;

}finally {

var diff = new Date() start;

}

alert( result ? result : 'была ошибка' ); alert( "Выполнение заняло " + diff );

Здесь секция finallyгарантирует, что время будет подсчитано в любых ситуациях — при ошибке в sumили без неё.

Вы можете проверить это, запустив код с указанием n=100— будет без ошибки, finally выполнится после try, а затем с n=100000— будет ошибка из-за слишком глубокой рекурсии, управление прыгнет в finallyпосле catch.

finallyи return

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

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

function func() {

try {

// сразу вернуть значение return 1;

}catch (e) { /* ... */

}finally {

alert( 'finally' );

}

}

alert( func() ); // сначала finally, потом 1

Если внутри tryбыли начаты какие-то процессы, которые нужно завершить по окончании работы, во в finallyэто обязательно будет сделано.

Кстати, для таких случаев иногда используют try..finallyвообще без catch:

function func() { try {

return 1; } finally {

alert( 'Вызов завершён' );

}

}

alert( func() ); // сначала finally, потом 1

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

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

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

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

В браузере существует специальное свойство window.onerror, если в него записать функцию,

то она выполнится и получит в аргументах сообщение ошибки, текущий URL и номер строки, откуда «выпала» ошибка.

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

Например:

<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— она позволяет обработать произвольные ошибки в блоке кода.

Это удобно в тех случаях, когда проще сделать действие и потом разбираться с результатом, чем долго и нудно проверять, не упадёт ли чего.

Кроме того, иногда проверить просто невозможно, например JSON.parse(str)не позволяет «проверить» формат строки перед разбором. В этом случае блок try..catch необходим.

Полный вид конструкции:

try {

.. пробуем выполнить код .. } catch(e) {

.. перехватываем исключение .. } finally {

.. выполняем всегда ..

}

Возможны также варианты try..catchили try..finally.

Оператор throw errгенерирует свою ошибку, в качестве errрекомендуется использовать объекты, совместимые с встроенным типом Error, содержащие свойства messageи name.

Кроме того, мы рассмотрели некоторые важные приёмы:

Проброс исключения — catch(err)должен обрабатывать только те ошибки, которые мы рассчитываем в нём увидеть, остальные — пробрасывать дальше через throw err.

Определить, нужная ли это ошибка, можно, например, по свойству name.

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

В window.onerrorможно присвоить функцию, которая выполнится при любой «выпавшей»

из скрипта ошибке. Как правило, это используют в информационных целях, например отправляют информацию об ошибке на специальный сервис.

Задачи

Finally или просто код?

важность: 5

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

1. Первый использует finallyдля выполнения кода по выходу из try..catch:

try {

начать работу работать

}catch (e) {

обработать ошибку

}finally {

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

}

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

try {

начать работу

}catch (e) {

обработать ошибку

}

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

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

Если имеет, то дайте пример когда код с finallyработает верно, а без — неверно.