web-dev-qa-db-ja.com

早期の解決/却下の後に戻る必要がありますか?

次のようなコードがあるとします。

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

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

  resolve(numerator / denominator);

 });
}

私の目的がrejectを使用して早期に終了することである場合、直後にもreturningの習慣を身に付ける必要がありますか?

184
sam

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. ペンディング - 初期状態。保留中から他の州の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));

現在そのようなコードが関数に含まれていなくても、これは将来のトラップとなる可能性があります。将来のリファクタリングは、約束が拒否された後もコードが実行されるという事実を無視する可能性があり、デバッグが困難になるでしょう。

resolve/reject:の後で実行を停止します

これは標準の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オプションの1つを使用することを好みます。

262
Ori Drori

returnrejectを組み合わせて、約束を拒否し、関数から出ることで、resolveを含む関数の残りの部分が実行されないようにするのが一般的な慣用句です。このスタイルが好きなら、それはあなたのコードをもう少しコンパクトにすることができます。

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

これは、Promiseコンストラクターが戻り値に対して何もしないため、resolverejectは何も返さないため、これは問題なく機能します。

別の答えに示すコールバックスタイルでも同じ慣用句を使用できます。

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

divideを呼び出している人は、何も返さないことを期待しておらず、戻り値に対して何もしていないので、これも問題なく動作します。

28
user663031

技術的にはここでは必要ありません1 - 約束は解決またはすることができるので、排他的に、一度だけ。最初の約束結果が勝ち、それ以降のすべての結果は無視されます。これはノードスタイルのコールバックとは異なっています

言われているのは、実用的な場合は必ず1つ、そしてこの場合はそれ以上のasync/deferredがないことを確かにするために良いクリーンプラクティスを確実にすることです。処理。 "早期返却"という決定は、その作業が完了したときに関数を終了させる - 対無関係な継続または不要な処理.

適切なタイミングで戻る(または「その他」のケースを実行しないように条件式を使用する)と、誤ったコードを無効な状態で実行したり、不要な副作用を実行したりする可能性が低くなります。そのため、コードが「予期せずに壊れる」可能性が低くなります。


1 この技術的な答えは、この場合コードがという事実にも依存します。 "return"の後は、省略しても副作用はありません。 JavaScriptは楽しく0で割って+ Infinity/-InfinityまたはNaNを返します。

9
user2864740

解決/却下の後に "戻る"ことをしないと、あなたがそれを止めようとした後に悪いこと(ページのリダイレクトなど)が起こる可能性があります。出典:私はこれに遭遇しました。

7
Benjamin H

Oriによる答えは既にそれがreturnに必要ではないことを説明しますが、それは良い習慣です。 promiseコンストラクタは安全にスローされるので、パスの後半で渡されたスローされた例外は無視されます。基本的に、簡単には確認できない副作用があります。

早期の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.
} 

それで、約束はグッドプラクティスですが、コールバックでは必須です。あなたのコードについてのいくつかのメモ:

  • あなたのユースケースは架空のものであり、実際には同期アクションを伴う約束を使用しないでください。
  • Promiseコンストラクタの戻り値を無視します。いくつかのライブラリはあなたがそこに戻ることの間違いに対してあなたに警告するためにあなたが未定義でない値を返すなら警告するでしょう。ほとんどはそんなに賢いわけではありません。
  • Promiseコンストラクターは安全であり、例外を却下に変換しますが、他の人が指摘したように - promiseは一度解決します。
7

多くの場合、パラメータを個別に検証して、拒否された約束を Promise.reject(reason) で即座に返すことができます。

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