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

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

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

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

  resolve(numerator / denominator);

 });
}

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

Ответ 1

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

Фон

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

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

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

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

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));

Ответ 2

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

Ответ 3

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

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

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


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

Ответ 4

Ответ от Ori уже объясняет, что нет необходимости 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 a callback.
} 

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

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

Ответ 5

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