Чи потрібно повернутися після раннього вирішення / відхилення?


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- оскільки значення повернення зворотного дзвінка ігнорується, ми можемо зберегти рядок, повернувши заяву про відхилення / вирішення:

  • Використання блоку if / else:

Я вважаю за краще використовувати один із returnваріантів, так як код більш плащ.


28
Варто зауважити, що код насправді не поводиться інакше, якщо він returnє чи ні, оскільки після встановлення стану обіцянки його неможливо змінити, тому виклик resolve()після виклику reject()нічого не зробить, крім використання декількох додаткових циклів процесора. Я сам би використовував returnсправедливий з точки зору чистоти та ефективності коду, але цього не потрібно в цьому конкретному прикладі.
jfriend00

1
Спробуйте використовувати Promise.try(() => { })замість нової Обіцянки та уникайте використання дзвінків для вирішення / відхилення. Натомість ви можете просто написати, що return denominator === 0 ? throw 'Cannot divide by zero' : numerator / denominator; я використовую Promise.tryяк засіб, щоб зняти Обіцянку, а також усунути обіцянки, загорнуті в блоки спробу / лову, які є проблематичними.
kingdango

2
Це добре знати, і мені подобається візерунок. Однак наразі Promise.try - це пропозиція на етапі 0, тому ви можете використовувати його лише з шиммом або за допомогою бібліотеки обіцянок, таких як bluebird або Q.
Ori Drori

6
@ jfriend00 Очевидно, що в цьому простому прикладі код поводиться не по-іншому. Але що робити, якщо у вас після цього був код, rejectце робить щось дороге, наприклад, підключення до баз даних або кінцевих точок API? Це все було б непотрібно і коштуватиме вам грошей та ресурсів, особливо, наприклад, якщо ви підключаєтесь до чогось типу бази даних AWS або кінцевої точки шлюзу API. У цьому випадку ви обов'язково використовуєте повернення, щоб уникнути непотрібного коду.
Джейк Вілсон

3
@JakeWilson - Звичайно, це просто нормальний потік коду в JavaScript і зовсім не має нічого спільного з обіцянками. Якщо ви закінчили обробку функції та не хочете більше виконувати код у поточному шляху коду, ви вставляєте a 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 для завершення виконання функції. Тримайте їх на окремих лініях, те, що ви робите, лише збентежить людей. Читання коду - це король.
K - Токсичність в SO зростає.

7
@KarlMorrison ти насправді повертаєш "щось", відхилену обіцянку. Я думаю, що "поняття", про яке ви говорите, дуже особисте. Немає нічого поганого у поверненні rejectстатусу
Frondor

5
@Frondor Я не думаю, що ти зрозумів те, що я написав. Звичайно, ми з вами це розуміємо, нічого не відбувається при поверненні відхилення в одному рядку. Але що з розробниками, які не так звикли, щоб JavaScript вступав у проект? Цей тип програмування зменшує читабельність для таких людей. Екосистема JavaScript сьогодні досить безладна, і люди, які поширюють цей тип практик, лише погіршать її. Це погана практика.
K - Токсичність в SO зростає.

1
@KarlMorrison Особисті думки! = Погана практика. Можливо, це допоможе новому розробнику Javascript зрозуміти, що відбувається з поверненням.
Тобі

1
@TobyCaulk Якщо людям потрібно дізнатися, що таке повернення, вони не повинні грати з Обіцянками, вони повинні вивчати базове програмування.
K - Токсичність в SO зростає.

10

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

Це, як кажуть, є хорошою чистою практикою щоб переконатися, що саме так називається, коли це практично, і справді в цьому випадку, оскільки немає ніякої подальшої асинхронізованої / відкладеної обробки. Рішення про "повернення достроково" не відрізняється від припинення будь-якої функції, коли її робота закінчена - проти продовження непов'язаної або непотрібної обробки.

Повернення у відповідний час (або використання інших умов для уникнення виконання "іншого" випадку) зменшує шанс випадкового запуску коду у недійсному стані або здійснення небажаних побічних ефектів; і як такий він робить код менш схильним до "несподіваного злому".


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


Приємна виноска !!
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));

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.