Ну, почти.
Подчищаем за исключениями
Представьте следующую ситуацию: функция 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
, предполагая, что он разбирается с другой проблемой, он считает, что выброшенное исключение является результатом неправильных входных данных. Это приводит к бесконечному циклу и скрывает полезное сообщение об ошибке насчёт неправильного имени переменной.