web-dev-qa-db-ja.com

キャッチ前後の配置

.catch BEFOREとAFTERを入れ子にしたプロミスに入れることの違いを理解するのに苦労しています。

代替案1:

test1Async(10).then((res) => {
  return test2Async(22)
    .then((res) => {
      return test3Async(100);
    }).catch((err) => {
      throw "ERROR AFTER THEN";
    });
}).then((res) => {
  console.log(res);
}).catch((err) => {
  console.log(err);
});

代替案2:

test1Async(10).then((res) => {
   return test2Async(22)
     .catch((err) => {
        throw "ERROR BEFORE THEN";
      })
      .then((res) => {
        return test3Async(100);
      });
  }).then((res) => {
    console.log(res);
  }).catch((err) => {
    console.log(err);
  });

各関数の動作は次のとおりです。数値が<0の場合、test1は失敗します。数値が> 10の場合、test2は失敗し、数値が100でない場合、test3は失敗します。この場合、test2は失敗しているだけです。

Test2Asyncを実行して失敗させようとしましたが、BEFOREとAFTERの両方が同じように動作し、test3Asyncを実行していません。誰かがキャッチを異なる場所に配置する主な違いを私に説明できますか?

各関数でconsole.log('Running test X')実行されるかどうかを確認します。

この質問は、私が以前に投稿したスレッド ネストされたコールバックを約束に変える方法? のために発生します。それは別の問題であり、別のトピックを投稿する価値があると思います。

76
Zanko

したがって、基本的に、これら2つの違いは何ですか(ここで、pは以前のコードから作成されたプロミスです):

return p.then(...).catch(...);

そして

return p.catch(...).then(...);

Pが解決または拒否するときに違いがありますが、それらの違いが重要かどうかは、.then()または.catch()ハンドラー内のコードが何をするかに依存します。

pが解決されるとどうなりますか:

最初のスキームでは、pが解決されると、.then()ハンドラーが呼び出されます。その.then()ハンドラーが値または最終的に解決する別のプロミスを返す場合、.catch()ハンドラーはスキップされます。ただし、.then()ハンドラーが最終的に拒否するプロミスをスローまたは返す場合、.catch()ハンドラーは、元のプロミスpの拒否とエラーが発生した場合に実行されます.then()ハンドラー内。

2番目のスキームでは、pが解決されると、.then()ハンドラーが呼び出されます。その.then()ハンドラーが最終的に拒否するプロミスをスローまたは返す場合、.catch()ハンドラーはチェーンの前にあるため、それをキャッチできません。

つまり、違い#1。.catch()ハンドラーがAFTERの場合、.then()ハンドラー内のエラーもキャッチできます。

pが拒否した場合の動作:

さて、最初のスキームでは、約束pが拒否された場合、.then()ハンドラーはスキップされ、.catch()ハンドラーが期待どおりに呼び出されます。 .catch()ハンドラーで行うことにより、最終結果として返されるものが決まります。 .catch()ハンドラーから値を返すか、最終的に解決するプロミスを返す場合、エラーを「処理」して正常に戻ったため、プロミスチェーンは解決済み状態に切り替わります。 .catch()ハンドラーで拒否されたプロミスをスローまたは返す場合、返されたプロミスは拒否されたままになります。

2番目のスキームでは、promise pが拒否された場合、.catch()ハンドラーが呼び出されます。 .catch()ハンドラーから最終的に解決する通常の値またはプロミスを返す場合(したがって、エラーを「処理する」)、promiseチェーンは解決された状態に切り替わり、.then()ハンドラーは.catch()が呼び出されます。

つまり、違い#2。.catch()ハンドラーがBEFOREである場合、エラーを処理し、.then()ハンドラーが呼び出されるようにすることができます。

使用する場合:

元のプロミスpまたは.catch()ハンドラーでエラーをキャッチできる.then()ハンドラーが1つだけで、pからの拒否が必要な場合は、最初のスキームを使用します.then()ハンドラー。

元のプロミスpのエラーをキャッチしたい場合は2番目のスキームを使用し、場合によっては(条件に応じて)プロミスチェーンを解決済みのまま続行し、.then()ハンドラーを実行します。

他のオプション

次のように、.then()に渡すことができる両方のコールバックを使用する別のオプションが1つあります。

 p.then(fn1, fn2)

これにより、fn1またはfn2のいずれか1つのみが呼び出されることが保証されます。 pが解決されると、fn1が呼び出されます。 pが拒否した場合、fn2が呼び出されます。 fn1の結果を変更しても、fn2が呼び出されることはなく、その逆もありません。そのため、ハンドラー自体で何が起こっているかに関係なく、2つのハンドラーのうちの1つだけが呼び出されるようにしたい場合は、p.then(fn1, fn2)を使用できます。

168
jfriend00

jfriend00's answer は優れていますが、類似の同期コードを追加することをお勧めします。

return p.then(...).catch(...);

同期に似ています:

try {
  iMightThrow() // like `p`
  then()
} catch (err) {
  handleCatch()
}

iMightThrow()がスローされない場合、then()が呼び出されます。スローする場合(またはthen()自体がスローする場合)、handleCatch()が呼び出されます。 catchブロックがthenが呼び出されるかどうかを制御しないことに注意してください。

一方、

return p.catch(...).then(...);

同期に似ています:

try {
  iMightThrow()
} catch (err) {
  handleCatch()
}

then()

この場合、iMightThrow()がスローされない場合、then()が実行されます。スローされた場合、handleCatch()が再スローされた場合、then()は呼び出されないため、handleCatch()が呼び出されるかどうかを決定するのはthen()までです。すぐに呼び出し元にスローされます。 handleCatch()が問題を適切に処理できる場合、then()が呼び出されます。

17
akivajgordon