Нужно ли возвращаться после досрочного разрешения / отклонения?

262

Предположим, у меня есть следующий код.

function divide(numerator, denominator) {
 return new Promise((resolve, reject) => {

  if(denominator === 0){
   reject("Cannot divide by 0");
   return; //superfluous?
  }

  resolve(numerator / denominator);

 });
}

Если моя цель состоит в том, чтобы использовать его rejectдля досрочного выхода, должен ли я привыкнуть returnсразу же после этого?

Сэм
источник

Ответы:

371

returnЦель состоит в том, чтобы прекратить выполнение функции после отказа, и предотвратить выполнение кода после него.

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {

    if (denominator === 0) {
      reject("Cannot divide by 0");
      return; // The function execution ends here 
    }

    resolve(numerator / denominator);
  });
}

В этом случае он предотвращает resolve(numerator / denominator);выполнение, что не является строго необходимым. Однако все еще предпочтительнее прекратить выполнение, чтобы предотвратить возможную ловушку в будущем. Кроме того, рекомендуется избегать ненужного запуска кода.

Задний план

Обещание может быть в одном из 3 состояний:

  1. в ожидании - начальное состояние. Из ожидающих мы можем перейти в одно из других состояний
  2. выполнено - успешная операция
  3. отклонено - сбой операции

Когда обещание выполнено или отклонено, оно будет оставаться в этом состоянии неопределенно (урегулировано). Таким образом, отказ от выполненного обещания или выполнение отклоненного обещания не будет иметь эффекта.

Фрагмент этого примера показывает, что, хотя обещание было выполнено после отклонения, оно осталось отклоненным.

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) {
      reject("Cannot divide by 0");
    }

    resolve(numerator / denominator);
  });
}

divide(5,0)
  .then((result) => console.log('result: ', result))
  .catch((error) => console.log('error: ', error));

Так зачем нам возвращаться?

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

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) {
      reject("Cannot divide by 0");
    }
    
    console.log('operation succeeded');

    resolve(numerator / denominator);
  });
}

divide(5, 0)
  .then((result) => console.log('result: ', result))
  .catch((error) => console.log('error: ', error));

Даже если функция не содержит такого кода прямо сейчас, это создает возможную ловушку в будущем. Будущий рефакторинг может игнорировать тот факт, что код все еще выполняется после отклонения обещания, и его будет сложно отладить.

Остановка выполнения после разрешения / отклонения:

Это стандартный поток управления JS.

  • Вернуться после resolve/ reject:

  • Возврат с resolve/ reject-, поскольку возвращаемое значение обратного вызова игнорируется, мы можем сохранить строку, вернув оператор reject / resol:

  • Используя блок if / else:

Я предпочитаю использовать один из returnвариантов, так как код более плоский.

Ори Дрори
источник
28
Стоит отметить, что код на самом деле не будет вести себя иначе, если returnон существует или нет, потому что после установки состояния обещания его нельзя изменить, поэтому вызов resolve()после вызова reject()ничего не изменит, кроме использования нескольких дополнительных циклов ЦП. Я сам использовал бы returnпросто с точки зрения чистоты и эффективности кода, но это не требуется в этом конкретном примере.
Jfriend00
1
Попробуйте использовать Promise.try(() => { })вместо нового Promise и избегайте использования разрешения / отклонения вызовов. Вместо этого вы можете просто написать « return denominator === 0 ? throw 'Cannot divide by zero' : numerator / denominator; Я использую» Promise.tryкак средство для запуска Promise, а также исключить обещания, заключенные в блоки try / catch, которые являются проблематичными.
кингданго
2
Это приятно знать, и мне нравится шаблон. Однако в настоящее время Promise.try является предложением стадии 0, поэтому его можно использовать только с прокладкой или с помощью библиотеки обещаний, такой как bluebird или Q.
Ori Drori
6
@ jfriend00 Очевидно, что в этом простом примере код не будет вести себя по-другому. Но что, если у вас есть код после того, rejectчто делает что-то дорогое, например, подключение к базам данных или конечные точки API? Все это было бы ненужным и стоило бы вам денег и ресурсов, особенно, например, если вы подключаетесь к чему-то вроде базы данных AWS или конечной точки шлюза API. В этом случае вы определенно будете использовать возврат, чтобы избежать выполнения ненужного кода.
Джейк Уилсон
3
@JakeWilson - Конечно, это обычный поток кода в Javascript, и он не имеет ничего общего с обещаниями. Если вы закончили обработку функции и не хотите, чтобы в текущем пути кода выполнялся какой-либо код, вставьте return.
Jfriend00
37

Распространенная идиома, которая может или не может быть вашей чашкой чая, состоит в том, чтобы объединить returnс reject, чтобы одновременно отклонить обещание и выйти из функции, так, чтобы остальная часть функции, включая функцию resolve, не была выполнена. Если вам нравится этот стиль, он может сделать ваш код немного более компактным.

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) return reject("Cannot divide by 0");
                           ^^^^^^^^^^^^^^
    resolve(numerator / denominator);
  });
}

Это работает отлично , так как конструктор Promise ничего не делает с любым возвращаемым значением, и в любом случае , resolveи rejectвозврата нет.

Та же идиома может использоваться со стилем обратного вызова, показанным в другом ответе:

function divide(nom, denom, cb){
  if(denom === 0) return cb(Error("Cannot divide by zero"));
                  ^^^^^^^^^
  cb(null, nom / denom);
} 

Опять же, это работает нормально, потому что вызывающий человек divideне ожидает, что он что-либо возвратит, и ничего не делает с возвращаемым значением.


источник
6
Мне это не нравится. Это дает представление о том, что вы возвращаете что-то, чем на самом деле не являетесь. Вы вызываете функцию отклонения и затем используете return, чтобы завершить выполнение функции. Держите их в отдельных строках, то, что вы делаете, только запутает людей. Читаемость кода - это главное.
К - Токсичность в СО растет.
7
@KarlMorrison вы на самом деле возвращаете «что-то», отклоненное обещание. Я думаю, что «понятие», о котором вы говорите, очень личное. Нет ничего плохого в возвращении rejectстатуса
Фрондор,
5
@ Фрондор Не думаю, что ты понял, что я написал. Конечно, вы и я это понимаем, ничего не происходит при возврате отказа на той же строке. Но как насчет разработчиков, которые не очень привыкли к JavaScript, входящему в проект? Этот тип программирования снижает читаемость для таких людей. Сегодня в экосистеме JavaScript достаточно беспорядка, и люди, распространяющие подобные практики, только усугубят ситуацию. Это плохая практика.
К - Токсичность в СО растет.
1
@KarlMorrison Личные мнения! = Плохая практика. Вероятно, это поможет новому разработчику Javascript понять, что происходит с возвратом.
Тоби
1
@TobyCaulk Если людям нужно узнать, что делает возврат, им не следует играть с Обещаниями, они должны изучать основы программирования.
К - Токсичность в СО растет.
10

Технически это здесь не нужно 1 - потому что Обещание может быть разрешено или отклонено исключительно и только один раз. Первый результат Promise выигрывает, и каждый последующий результат игнорируется. Это отличается от обратных вызовов в стиле Node.

Это, как говорится, хорошая чистая практика, чтобы гарантировать, что точно один вызывается, когда это практически осуществимо, и, действительно, в этом случае, поскольку дальнейшая асинхронная / отложенная обработка не выполняется. Решение «вернуться рано» ничем не отличается от завершения любой функции, когда ее работа завершена, - против продолжения несвязанной или ненужной обработки.

Возврат в подходящее время (или иное использование условных выражений во избежание выполнения «другого» случая) снижает вероятность случайного запуска кода в недопустимом состоянии или выполнения нежелательных побочных эффектов; и как таковой он делает код менее подверженным неожиданному взлому.


1 Это технически ответ зависит также от того , что в этом случае код после «возвращения», она должна быть опущена, не приведет к побочному эффекту. JavaScript с радостью разделится на ноль и вернет либо + Infinity / -Infinity, либо NaN.

user2864740
источник
Хорошая сноска!
HankCa
9

Если вы не «вернетесь» после разрешения / отклонения, могут произойти плохие вещи (например, перенаправление страницы) после того, как вы намеревались остановить его. Источник: я столкнулся с этим.

Бенджамин Н
источник
6
+1 за пример. У меня была проблема, когда моя программа делала более 100 недопустимых запросов к базе данных, и я не мог понять, почему. Оказывается, я не «вернулся» после отказа. Это небольшая ошибка, но я усвоил урок.
AdamInTheOculus
8

Ответ Ори уже объясняет, что в этом нет необходимости, returnно это хорошая практика. Обратите внимание, что конструктор обещания безопасен для выброса, поэтому он будет игнорировать генерируемые исключения, переданные позже в пути, по сути, у вас есть побочные эффекты, которые вы не можете легко заметить.

Обратите внимание, что returnранний вызов также очень распространен в обратных вызовах:

function divide(nom, denom, cb){
     if(denom === 0){
         cb(Error("Cannot divide by zero");
         return; // unlike with promises, missing the return here is a mistake
     }
     cb(null, nom / denom); // this will divide by zero. Since it's a callback.
} 

Таким образом, хотя это хорошая практика в обещаниях, она требуется для обратных вызовов. Некоторые заметки о вашем коде:

  • Ваш вариант использования является гипотетическим, на самом деле не используйте обещания с синхронными действиями.
  • Конструктор обещаний игнорирует возвращаемые значения. Некоторые библиотеки будут предупреждать, если вы вернете неопределенное значение, чтобы предупредить вас об ошибке возврата туда. Большинство не такие умные.
  • Конструктор обещаний безопасен, он преобразует исключения в отклонения, но, как отмечали другие, обещание разрешается один раз.
Бенджамин Грюнбаум
источник
4

Во многих случаях можно проверить параметры отдельно и немедленно вернуть отклоненное обещание с помощью Promise.reject (причина) .

function divide2(numerator, denominator) {
  if (denominator === 0) {
    return Promise.reject("Cannot divide by 0");
  }
  
  return new Promise((resolve, reject) => {
    resolve(numerator / denominator);
  });
}


divide2(4, 0).then((result) => console.log(result), (error) => console.log(error));

Dorad
источник