Как стать автором
Обновить
2680.93
RUVDS.com
VDS/VPS-хостинг. Скидка 15% по коду HABR15

В JS-функциях «побеждает» последний оператор return

Время на прочтение 3 мин
Количество просмотров 21K
Автор оригинала: Jake Archibald
Вот — JavaScript-функция. Какой из операторов 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, которой посвящена эта статья?

Теги:
Хабы:
+26
Комментарии 35
Комментарии Комментарии 35

Публикации

Информация

Сайт
ruvds.com
Дата регистрации
Дата основания
Численность
11–30 человек
Местоположение
Россия
Представитель
ruvds