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

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

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

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

  resolve(numerator / denominator);

 });
}

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


person sam    schedule 12.09.2015    source источник


Ответы (6)


Цель 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);, что не является строго необходимым. Однако все же предпочтительнее прекратить выполнение, чтобы предотвратить возможную ловушку в будущем. Кроме того, рекомендуется предотвращать ненужный запуск кода.

Фон

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

  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:

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

    console.log('operation succeeded');

    resolve(numerator / denominator);
  });
}

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

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

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) {
      return 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));

  • Использование блока if / else:

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

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

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

person Ori Drori    schedule 12.09.2015
comment
Стоит отметить, что код на самом деле не будет вести себя иначе, если return есть или нет, потому что после того, как состояние обещания было установлено, его нельзя изменить, поэтому вызов resolve() после вызова reject() не будет делать ничего, кроме использования нескольких дополнительных ЦП. циклы. Я бы использовал return только с точки зрения чистоты и эффективности кода, но в данном конкретном примере это не требуется. - person jfriend00; 12.09.2015
comment
Попробуйте использовать Promise.try(() => { }) вместо new Promise и избегайте вызовов разрешения / отклонения. Вместо этого вы можете просто написать return denominator === 0 ? throw 'Cannot divide by zero' : numerator / denominator; Я использую Promise.try как средство для запуска обещания, а также для устранения обещаний, заключенных в блоки try / catch, которые являются проблематичными. - person kingdango; 27.09.2016
comment
Это хорошо знать, и мне нравится этот узор. Однако в настоящее время Promise.try является предложением этапа 0, поэтому вы можете только используйте его с прокладкой или с помощью библиотеки обещаний, такой как bluebird или Q . - person Ori Drori; 27.09.2016
comment
@ jfriend00 Очевидно, что в этом простом примере код не будет вести себя иначе. Но что, если бы у вас был код после reject, который выполняет что-то дорогостоящее, например, подключается к базам данных или конечным точкам API? Все это было бы ненужным и стоило вам денег и ресурсов, особенно, если вы подключаетесь к чему-то вроде базы данных AWS или конечной точки API Gateway. В этом случае вы обязательно воспользуетесь возвратом, чтобы избежать выполнения ненужного кода. - person Jake Wilson; 20.10.2016
comment
@JakeWilson - Конечно, это просто нормальный поток кода в Javascript и не имеет никакого отношения к обещаниям. Если вы закончили обработку функции и больше не хотите, чтобы код выполнялся в текущем пути кода, вы вставляете return. - person jfriend00; 20.10.2016
comment
Согласен с @JakeWilson. Между reject и resolve может быть больше кода. Можно ли делать return reject();? Я предполагаю, что reject просто возвращает undefined или что-то в этом роде .. - person Luke; 11.01.2017
comment
@Luke - действительно - reject() возвращает undefined, и вы можете return это, если вам нужно прервать поток, как описано в jfriend00. - person Ori Drori; 11.01.2017
comment
@ Люк, хорошо бы вернуться, если после этого могут произойти другие операции. Если у вас была фоновая асинхронная задача (например, обновление таблицы базы данных) в вашем коде после reject(), вы должны убедиться, что вы return reject(...) или return после reject(). reject() и resolve() только сообщают обещанию, что переданное значение - это то, чего он ждал. Это не останавливает выполнение кода для этого блока кода. - person Jake Wilson; 12.01.2017
comment
@Luke Сохраните возврат в отдельной строке. Тогда вы абсолютно ЗНАЕТЕ, что возвращаетесь undefined. Кто знает, какой отказ может возвращаться. - person basickarl; 12.12.2017

Распространенная идиома, которая может быть вашей чашкой чая, а может и не быть, состоит в том, чтобы объединить 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, не ожидает, что он что-то вернет, и ничего не делает с возвращаемым значением.

person Community    schedule 19.03.2016
comment
Мне это не нравится. Это дает представление о том, что вы возвращаете то, чем на самом деле не являетесь. Вы вызываете функцию отклонения, а затем используете return для завершения выполнения функции. Храните их в отдельных строках, то, что вы делаете, только запутает людей. Читаемость кода - это король. - person basickarl; 12.12.2017
comment
@KarlMorrison, ты действительно что-то возвращаешь, отклоненное обещание. Я думаю, что идея, о которой вы говорите, очень личное. Нет ничего плохого в том, чтобы вернуть статус reject - person Frondor; 01.03.2018
comment
@Frondor Не думаю, что вы поняли, что я написал. Конечно, мы с вами это понимаем, при возврате отклонения в той же строке ничего не происходит. Но как насчет разработчиков, которые не так привыкли к JavaScript, которые входят в проект? Такой тип программирования снижает удобочитаемость для таких людей. Экосистема JavaScript сегодня достаточно беспорядочная, и люди, распространяющие такие практики, только усугубят ее. Это плохая практика. - person basickarl; 01.03.2018
comment
@KarlMorrison Личное мнение! = Плохая практика. Вероятно, это поможет новому разработчику Javascript понять, что происходит с возвратом. - person Toby Caulk; 30.01.2019
comment
@TobyCaulk Если людям нужно узнать, что дает возврат, им не следует играть с обещаниями, им следует изучать основы программирования. - person basickarl; 30.01.2019
comment
@KarlMorrison Это полная противоположность. Новые разработчики Javascript, скорее всего, знают, что значит вернуть что-то, но они еще не понимают, что означает простой вызов reject / resolve. Это более ясно, я не знаю, почему это так важно. - person Toby Caulk; 31.01.2019
comment
@TobyCaulk Точно, большинство разработчиков понимают возврат, но не отказ. Возвращая отклонение, он заставляет младших разработчиков думать, что отклонение возвращает значение из своего вызова функции, а это не так. jsfiddle.net/tf9exkvn - person basickarl; 03.02.2019
comment
@Frondor Ваш комментарий - отличный пример того, кого смущает именно эта практика. Вы не возвращаете что-то с помощью return, вы что-то возвращаете при вызове reject (), это две совершенно разные вещи. Вот вам код: jsfiddle.net/tf9exkvn Кроме того, это не личное, речь идет о том, чтобы поднять младших разработчиков. чтобы ускорить начало работы над крупными проектами и убедиться, что они полностью понимают обещания. - person basickarl; 03.02.2019

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

person Benjamin H    schedule 12.12.2016
comment
+1 для примера. У меня возникла проблема, из-за которой моя программа выполняла более 100 недопустимых запросов к базе данных, и я не мог понять, почему. Оказывается, я не вернулся после отказа. Это небольшая ошибка, но я усвоил урок. - person AdamInTheOculus; 23.03.2017

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

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

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


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

person user2864740    schedule 12.09.2015
comment
Хорошая сноска !! - person HankCa; 17.04.2019

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

Обратите внимание, что раннее returning также очень часто используется в обратных вызовах:

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.
} 

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

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

Во многих случаях можно проверить параметры отдельно и немедленно вернуть отклоненное обещание с помощью 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));

person Dorad    schedule 24.11.2018