return
, имеющихся в ней, «победит» при вызове этой функции?
function test() {
return 'one';
return 'two';
return 'three';
}
Вероятно, вы скажете, что это — первый
return
. А вот я хочу попытаться убедить вас в том, что «победителем» окажется return
последний.
Будьте спокойны: эта функция, определённо, возвращает 'one'
. Но в данном случае первый return
не даёт выполняться остальным. В результате «последний return
» — это и есть return 'one'
, именно он и «побеждает» другие операторы return
. Конечно, это — и самый первый такой оператор, но при этом то, что я заявил выше, остаётся истинным (я, говоря это, с самодовольным видом скрещиваю руки на груди).
Знаю, в вашей голове сейчас проносится примерно такая мысль: «Да заткнись уже!». Но я, всё же, прошу вас ещё немного меня потерпеть…
Блок finally
Блок
finally
— это вещь:
function finallyTest() {
try {
console.log('one');
return 'three';
} catch (err) {
console.log('error');
} finally {
console.log('two');
}
}
console.log(finallyTest());
console.log('four');
После запуска вышеприведённого кода в консоль попадёт
'one'
, 'two'
, 'three'
, 'four'
. Блок finally
всегда выполняется после блоков try
/catch
— даже в том случае, если в них есть операторы return
.
Я до последнего времени не особенно часто пользовался в JavaScript блоком finally
, а недавно применил его в асинхронной функции. Выглядело это примерно так:
async function someAsyncThing() {
startSpinner();
try {
await asyncWork();
} catch (err) {
if (err.name === 'AbortError') return;
showErrorUI();
} finally {
stopSpinner();
}
}
Но, в любом случае, замечательной особенностью
finally
является тот факт, что этот блок даёт возможность осуществить несколько возвратов при выполнении единственного вызова функции.
function manyHappyReturns() {
try {
return 'one';
} finally {
try {
return 'two';
} finally {
return 'three';
}
}
}
Результатом вызова
manyHappyReturns()
будет 'three'
.
Последний оператор return
всегда «побеждает» другие. Не тот, который находится последним в теле функции, что было бы безумием, а тот, который выполняется последним. И речь тут идёт о такой же «победе», которую одерживает последняя операция присвоения значения одной и той же переменной. При этом те операции присвоения значения, которые не были выполнены, мы не учитываем. На самом деле, спецификация return
очень похожа на спецификацию операции присвоения значения переменной. Так, return
присваивает вызову функции некий результат. Поэтому следующий вызов return
перекрывает то, что было возвращено после предыдущего вызова return
. То же самое справедливо и для Java, и для Python. Благодарю Даниэля Еренберга за то, что обратил моё внимание на эту небольшую особенность return
.
Побочным следствием этой особенности return
является тот факт, что использование этого оператора в блоке finally
приводит к очистке выброшенной ранее ошибки.
function catchThis() {
try {
throw Error('boom');
} finally {
return 'phew';
}
}
Результатом вызова
catchThis()
будет 'phew'
.
▍Как это можно применить на практике?
Полагаю, практического применения у вышеописанной особенности
return
нет. Спасибо, что дочитали до этого места! И ещё — прошу вас — не задавайте вопросов об этом на собеседованиях.
Бонус: пара слов о промисах
Асинхронные функции ведут себя так же, как описано выше (за исключением того, что они возвращают промисы). Но конструкция
promise.finally()
ведёт себя иначе.
const promise = Promise.resolve('one').finally(() => 'two');
Тут
promise
успешно разрешается значением 'one'
. Вероятно, причиной этого является тот факт, что результатом работы промиса является вызов некоего коллбэка, при этом то, что вызывает коллбэк (в данном случае — сам промис) не имеет возможности отличить функцию, в которой выполняется вызов return undefined
, от функции, в которой вообще нет оператора return
. В результате promise.finally()
не может воспроизвести вышеописанный пограничный случай, характерный для блока finally
, и просто его игнорирует.
Правда, promise.finally()
оказывает влияние на то, когда именно промис будет разрешён:
const wait = (ms) => new Promise((r) => setTimeout(() => r(), ms));
const promise = Promise.resolve('one').finally(async () => {
await wait(2000);
return 'two';
});
В данном случае
promise
, всё так же, разрешается значением 'one'
, но на это у него теперь уходит две секунды.
Приходилось ли вам сталкиваться с особенностью return, которой посвящена эта статья?
Комментариев нет:
Отправить комментарий