Обратите внимание, что функция lookполностью игнорирует возможность возникновения проблем в promptDirection. Это преимущество исключений – код, обрабатывающий ошибки, нужен только в том месте, где происходит ошибка, и там, где она обрабатывается. Промежуточные функции просто не обращают на это внимания.
Ну, почти.
Подчищаем за исключениями
Представьте следующую ситуацию: функция withContextжелает удостовериться, что во время её выполнения переменная верхнего уровня contextсодержит специальное значение контекста. В конце выполнения функция восстанавливает прежнее значение переменной.
var context = null;
function withContext(newContext, body) {
var oldContext = context;
context = newContext;
var result = body();
context = oldContext;
return result;
}
Что, если функция bodyвыбросит исключение? В таком случае вызов withContextбудет выброшен исключением из стека, и переменной contextникогда не будет возвращено первоначальное значение.
Но у инструкции tryесть ещё одна особенность. За ней может следовать блок finally, либо вместо catch, либо вместе с catch. Блок finallyозначает «выполнить код в любом случае после выполнения блока try». Если функции надо что-то подчистить, то подчищающий код нужно включать в блок finally.
function withContext(newContext, body) {
var oldContext = context;
context = newContext;
try {
return body();
} finally {
context = oldContext;
}
}
Заметьте, что нам больше не нужно сохранять результат вызова bodyв отдельной переменной, чтобы вернуть его. Даже если мы возвращаемся из блока try, блок finallyвсё равно будет выполнен. Теперь мы можем безопасно сделать так:
try {
withContext(5, function() {
if (context < 10)
throw new Error("Контекст слишком мал!");
});
} catch (e) {
console.log("Игнорируем: " + e);
}
// → Игнорируем: Error: Контекст слишком мал!
console.log(context);
// → null
Несмотря на то, что вызываемая из withContextфункция «сломалась», сам по себе withContextпо-прежнему подчищает значение переменной context.
Выборочный отлов исключений
Когда исключение доходит до низа стека и его никто не поймал, его обрабатывает окружение. Как именно – зависит от конкретного окружения. В браузерах описание ошибки выдаётся в консоль (она обычно доступна в меню «Инструменты» или «Разработка»).
Если речь идёт об ошибках или проблемах, которые программа не может обработать в принципе, допустимо просто пропустить такую ошибку. Необработанное исключение – разумный способ сообщить о проблеме в программе, и консоль в современных браузерах выдаст вам необходимую информацию о том, какие вызовы функций были в стеке в момент возникновения проблемы.
Если возникновение проблемы предсказуемо, программа не должна падать с необработанным исключением – это не очень дружественно по отношению к пользователю.
Недопустимое использование языка – ссылки на несуществующую переменную, запрос свойств у переменной, равной null, или вызов чего-то, что не является функцией – тоже приводит к выбрасыванию исключений. Такие исключения можно отлавливать точно так же, как свои собственные.
При входе в блок catchмы знаем только, что что-то внутри блока tryпривело к исключению. Мы не знаем, что именно, и какое исключение произошло.
JavaScript (что является вопиющим упущением) не предоставляет непосредственной поддержки выборочного отлова исключений: либо ловим все, либо никакие. Из-за этого люди часто предполагают, что случившееся исключение – именно то, ради которого и писался блок catch.
Но может быть и по-другому. Нарушение произошло где-то ещё, или в программу вкралась ошибка. Вот пример, где мы пробуем вызывать promptDirectionдо тех пор, пока не получим допустимый ответ:
for (;;) {
try {
var dir = promtDirection("Куда?"); // ← опечатка!
console.log("Ваш выбор", dir);
break;
} catch (e) {
console.log("Недопустимое направление. Попробуйте ещё раз.");
}
}
Конструкция for (;;)– способ устроить бесконечный цикл. Мы вываливаемся из него, только когда получаем допустимое направление. Но мы неправильно написали название promptDirection, что приводит к ошибке “undefined variable”. А так как блок catchигнорирует значение исключения e, предполагая, что он разбирается с другой проблемой, он считает, что выброшенное исключение является результатом неправильных входных данных. Это приводит к бесконечному циклу и скрывает полезное сообщение об ошибке насчёт неправильного имени переменной.
Читать дальше