finally
Вернёмся к цепочке поручений. Стажёр берёт со стола калькулятор, а закончив, кладёт его на место. Но если работа сорвалась и он бросил наверх записку с причиной, про калькулятор он может забыть. Следующий стажёр придёт и не найдёт его.
В программе так бывает с переменными, которые код меняет на время работы. Когда в блоке try брошена ошибка, строки после неё не выполняются. Если среди них была важная строка, она тоже пропадёт. Функция load разбирает текст JSON и на это время ставит флаг — переменную loading со значением true:
let loading = false;
function load(text) {
loading = true;
const data = JSON.parse(text);
loading = false;
return data;
}
try {
load("{ bad");
} catch (e) {
console.log("caught"); // caught
}
console.log(loading); // trueДавайте разберём, что случилось:
loadзаписала вloadingзначениеtrue.JSON.parseбросилSyntaxErrorна неверном тексте.- Функция прервалась раньше строки
loading = false. - Ошибку поймал
catchснаружи и напечаталcaught. - Флаг остался
true: программа считает, что загрузка всё ещё идёт.
Блок finally
После try и catch можно добавить блок finally. Это и есть «положить калькулятор на место»: блок выполняется при любом исходе. Таких исходов три:
tryпрошёл без ошибок;- ошибку поймал
catch; - ошибка вышла из функции наружу.
Поэтому в finally пишут то, что должно случиться в любом случае. Если сработал catch, finally выполняется после него.
Чаще всего finally отменяет то, что код сделал на время работы: сбрасывает флаг или возвращает счётчику прежнее значение.
function load(text) {
loading = true;
try {
return JSON.parse(text);
} finally {
loading = false;
}
}Разберём, что изменилось:
loading = trueпо-прежнему стоит в начале функции.- Разбор текста переехал в
try, а сброс флага — вfinally. - Блока
catchздесь нет: ошибку изJSON.parseловит вызывающий код, как в первом примере. - Прежде чем ошибка выйдет из функции,
finallyсбросит флаг, иconsole.log(loading)напечатаетfalse.
Пара try и finally без catch допустима, а try без catch и без finally — нет.
finally и return
finally не пропускается и тогда, когда try завершается через return. Функция запоминает возвращаемое значение, затем выполняет finally, и только после этого вызывающий код получает значение:
function count() {
try {
console.log("try"); // try
return 1;
} finally {
console.log("finally"); // finally
}
}
console.log(count()); // 1Давайте посмотрим на порядок строк:
tryпечатаетtry.return 1запоминает результат, но функция ещё не закончилась.finallyпечатаетfinally.- Только теперь внешний
console.logполучает1и печатает его последним.
Этот порядок часто проверяют на собеседовании: «Выполнится ли finally, если в try стоит return?» Выполнится, и до того, как вызывающий код получит значение.
Здесь аналогия со стажёром перестаёт работать: человек может забыть про калькулятор, а finally не забывает никогда. Даже return в try его не отменяет.
Частая ошибка
В finally написали return:
function load(text) {
try {
return JSON.parse(text);
} finally {
return null; // ✗
}
}Сообщения об ошибке не будет, но функция сломана. return в finally заменяет значение из try, поэтому load('{"a":1}') возвращает null. Брошенную ошибку он тоже отменяет: для "{ bad" функция тихо вернёт null, и catch снаружи ничего не получит. В finally оставляют только действия вроде loading = false, без return.
Главное
finallyвыполняется при любом исходе: без ошибки, послеcatchи когда ошибка выходит наружу.- В
finallyвозвращают на место то, что код поменял на время работы, например сбрасывают флаг. - После
returnвtryблокfinallyвыполняется до того, как вызывающий код получит значение. returnвfinallyзаменяет результат и отменяет брошенную ошибку, поэтому его там не пишут.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.