return: результат функции

До сих пор наши функции в основном печатали результат через console.log. Но иногда результат нужен не человеку, а самой программе – например, чтобы сохранить его в переменную, использовать в другом вычислении или проверить в условии.

Для этого функция должна вернуть значение. В JavaScript это делает return:

order-total.jsjs
function orderTotal(price, count) {  return price * count;}const total = orderTotal(90, 3);console.log(total);
Консоль
270

orderTotal(90, 3) возвращает 270, поэтому после выполнения весь вызов можно воспринимать как обычное значение:

запись в коде
const total = orderTotal(90, 3);
что получается после вызова
const total = 270;
Разница лишь в том, что число 270 программа получила, выполнив функцию.

Что можно написать после return

После return можно указать готовое значение, переменную или выражение:

return-forms.jsjs
return 300;return price;return price * count;

В последнем случае сначала вычислится price * count, а функция вернёт получившийся результат. За один вызов функция возвращает одно значение. Посмотрим весь путь значения по шагам:

Вызов: аргументы внутрь – результат наружу
function orderTotal(price, count) {
return price * count;
}
const total = orderTotal(90, 3);
Вызов: выполнение прыгает в тело функции, аргументы 90 и 3 легли в параметры.
console.log(total);

console.log показывает, return возвращает

Здесь легко запутаться. Если функция что-то напечатала в консоли, может показаться, что это и есть её результат. Но вывод в консоль и результат функции – разные вещи.

Посмотрим на обычную функцию суммы:

sum-log.jsjs
function sum(a, b) {  console.log(a + b);}const result = sum(2, 3);console.log(result);
Консоль
5
undefined

Первую 5 напечатала сама функция – сработал console.log(a + b). Но затем мы отдельно попросили вывести result и получили undefined. Значит, функция показала сумму в консоли, но наружу её не передала.

Чтобы результат вызова можно было сохранить и использовать дальше, его нужно вернуть:

sum-return.jsjs
function sum(a, b) {  return a + b;}const result = sum(2, 3);console.log(result);
Консоль
5

Теперь всё иначе: sum(2, 3) возвращает 5, и это значение попадает в result. Поэтому полезно разделять две вещи: console.log(...) показывает значение человеку, return отдаёт значение программе.

С результатом вызова можно работать

После return вызов функции ведёт себя как обычное значение. Его необязательно сначала сохранять в переменную – можно использовать там же, где вы использовали бы число, строку или другое значение.

Сохранить:

save-result.jsjs
const price = double(50);

Использовать в выражении или условии:

call-in-expression.jsjs
const finalPrice = double(50) + 20;if (double(60) > 100) {  console.log('Дорого');}

Передать другой функции:

chain-calls.jsjs
addTen(double(5));

Сначала double(5) возвращает 10, затем получается addTen(10), который возвращает 20. Так функции соединяются друг с другом: одна возвращает результат, а другая использует его дальше.

Но у вложенности есть цена – читаемость: чем больше вызовов внутри вызовов, тем труднее следить за порядком вычислений. Если цепочка разрастается, промежуточный результат лучше положить в переменную с понятным именем:

Промежуточные переменные с именами
const step1 = addTen(double(5));  // 20// 50const step2 = addTen(double(step1));console.log(step2);
Глубокая вложенность
console.log(addTen(double(addTen(double(5)))));

Возвращать можно разные значения

is-adult.jsjs
function isAdult(age) {  return age >= 18;}console.log(isAdult(20));console.log(isAdult(14));
Консоль
true
false

Здесь выражение age >= 18 даёт true или false, поэтому одно из этих значений и становится результатом функции. Позже функции будут возвращать также строки, массивы, объекты и другие значения.

return завершает текущий вызов

До сих пор мы смотрели на return как на способ получить результат из функции. Но у него есть ещё одно важное свойство: как только JavaScript встречает return, текущий вызов заканчивается. Всё, что находится ниже, в этом вызове уже не выполняется:

after-return.jsjs
function test() {  console.log('до');  return 5;  console.log('после');}console.log(test());
Консоль
до
5

Функция успела напечатать до, затем дошла до return 5 и сразу закончила работу. Поэтому строка console.log('после') недостижима.

При этом следующий вызов test() снова начнёт выполнение функции с первой строки: завершается вызов, а не сама функция.

Иногда нужно просто выйти

Если главная задача return в конкретном месте – остановить функцию, возвращать значение необязательно. Можно написать просто return; – вызов закончится точно так же, а его результатом станет undefined:

submit-order.jsjs
function submitOrder(total) {  if (total <= 0) {    console.log('Корзина пуста');    return;  }  console.log('Заказ отправлен');}submitOrder(1200);submitOrder(0);
Консоль
Заказ отправлен
Корзина пуста

Если сумма заказа равна нулю, отправлять заказ дальше бессмысленно. Функция сообщает о проблеме и заканчивает текущий вызов через return. Поэтому до строки console.log('Заказ отправлен') пустая корзина вообще не доберётся.

Ту же проверку можно записать и привычным if..else – обе версии работают одинаково:

ранний return
if (total <= 0) {  console.log('Корзина пуста');  return;}console.log('Заказ отправлен');
if..else
if (total <= 0) {  console.log('Корзина пуста');} else {  console.log('Заказ отправлен');}

Пока проверка одна, оба варианта читаются одинаково хорошо – выбирайте любой. Разница появляется, когда проверок несколько: каждая новая ветка else вкладывает код на уровень глубже, а return держит все проверки на одном уровне. Это и показывает следующая секция.

Ранний выход

Допустим, заказ можно оформить, только если он оплачен и указан адрес. Без раннего выхода можно написать так:

checkout-nested.jsjs
function checkout(isPaid, hasAddress) {  if (isPaid) {    if (hasAddress) {      return 'Заказ оформлен';    } else {      return 'Укажите адрес';    }  } else {    return 'Сначала оплатите заказ';  }}

Это работает. Но читать такую функцию неудобно: основная логика всё глубже уезжает вправо. Проще сначала проверить причины, по которым продолжать нельзя. Условия при этом переворачиваются: в начало функции поднимается то, что раньше лежало в ветках else, – и каждый такой случай сразу завершает вызов через return:

checkout.jsjs
function checkout(isPaid, hasAddress) {  if (!isPaid) {    return 'Сначала оплатите заказ';  }  if (!hasAddress) {    return 'Укажите адрес';  }  return 'Заказ оформлен';}console.log(checkout(true, false));console.log(checkout(true, true));
Консоль
Укажите адрес
Заказ оформлен

Функция идёт сверху вниз. Если заказ не оплачен – ответ уже известен, поэтому продолжать незачем: return сразу завершает вызов. Если с оплатой всё хорошо, проверяем адрес. Нет адреса – снова сразу выходим. До последней строки доходят только заказы, которые прошли обе проверки.

Плохой случай? Разобрались с ним и вышли. Хороший случай идёт дальше.

// теперь вы знаете

  1. return вычисляет выражение справа и возвращает получившееся значение – одно за вызов
  2. Вызов функции становится значением: его можно сохранить, сравнить, передать дальше
  3. console.log показывает значение человеку, return отдаёт его программе
  4. return завершает текущий вызов: код ниже не выполняется, а return; возвращает undefined
  5. Ранний выход: плохой случай – разобрались и вышли, хороший идёт дальше

Проверьте себя

Что появится в консоли?

function double(x) {
return x * 2;
}
console.log(double(4));

Что появится в консоли?

function hello() {
console.log('привет');
}
console.log(hello());

Что НЕ появится в консоли?

function test() {
console.log('до');
return 5;
console.log('после');
}
console.log(test());

Появится ли в консоли «Заказ отправлен»?

function submitOrder(total) {
if (total <= 0) {
return;
}
console.log('Заказ отправлен');
}
submitOrder(0);
переключают уроки · не работают при фокусе в полях ввода