Promise チェーンはエラー処理に優れています。 Promise が拒否されると、コントロールは最も近い拒否ハンドラーにジャンプします。それは実際には非常に便利です。
たとえば、以下のコードでは、 fetch
URL が間違っており (そのようなサイトはありません) .catch
エラーを処理します。
fetch('https://no- such-server.blabla') // 拒否します .then(応答 => 応答.json()) .catch(err =>alert(err)) // TypeError: フェッチに失敗しました (テキストは異なる場合があります)
ご覧のとおり、 .catch
は即時である必要はありません。 1 つまたは複数の.then
の後に表示される場合があります。
あるいは、サイトに問題はなくても、応答が有効な JSON ではない可能性があります。すべてのエラーをキャッチする最も簡単な方法は、チェーンの最後に.catch
を追加することです。
fetch('https://javascript.info/article/promise-chaining/user.json') .then(応答 => 応答.json()) .then(user => fetch(`https://api.github.com/users/${user.name}`)) .then(応答 => 応答.json()) .then(githubUser => new Promise((解決、拒否) => { let img = document.createElement('img'); img.src = githubUser.avatar_url; img.className = "約束のアバターの例"; document.body.append(img); setTimeout(() => { img.remove(); 解決(githubUser); }, 3000); })) .catch(エラー => アラート(エラー.メッセージ));
通常、このような.catch
まったくトリガーされません。ただし、上記の Promise のいずれかが拒否された場合 (ネットワークの問題や無効な JSON など)、それをキャッチします。
Promise Executor と Promise ハンドラーのコードには、その周りに「目に見えないtry..catch
」があります。例外が発生すると、それが捕捉され、拒否として扱われます。
たとえば、このコードは次のようになります。
new Promise((解決、拒否) => { throw new Error("おっと!"); }).catch(アラート); // エラー: おっと!
…これとまったく同じように動作します。
new Promise((解決、拒否) => { 拒否(新しいエラー(「おっと!」)); }).catch(アラート); // エラー: おっと!
executor の周囲にある「目に見えないtry..catch
」がエラーを自動的にキャッチし、拒否された Promise に変換します。
これはエグゼキューター関数だけでなく、そのハンドラーでも発生します。 .then
ハンドラー内にthrow
と、Promise が拒否されたことを意味するため、コントロールは最も近いエラー ハンドラーにジャンプします。
以下に例を示します。
new Promise((解決、拒否) => { 解決("ok"); }).then((結果) => { throw new Error("おっと!"); // プロミスを拒否します }).catch(アラート); // エラー: おっと!
これは、 throw
ステートメントによって発生したエラーだけでなく、すべてのエラーで発生します。たとえば、プログラミング エラー:
new Promise((解決、拒否) => { 解決("ok"); }).then((結果) => { ブラブラ(); // そのような関数はありません }).catch(アラート); // ReferenceError: blabla が定義されていません
最後の.catch
明示的な拒否だけでなく、上記のハンドラーでの偶発的なエラーもキャッチします。
すでに気づいたように、チェーンの最後にある.catch
はtry..catch
に似ています。必要な数の.then
ハンドラーを用意し、最後に 1 つの.catch
使用してすべてのエラーを処理できます。
通常のtry..catch
ではエラーを分析し、処理できない場合は再スローできます。同じことがpromiseでも可能です。
.catch
内にthrow
と、コントロールは次に近いエラー ハンドラーに移動します。そして、エラーを処理して正常に終了すると、次に近い成功した.then
ハンドラーに進みます。
以下の例では、 .catch
エラーを正常に処理します。
// 実行: catch -> then new Promise((解決、拒否) => { throw new Error("おっと!"); }).catch(関数(エラー) { alert("エラーは処理されました。通常どおり続行します"); }).then(() =>alert("次に成功したハンドラーが実行されます"));
ここで、 .catch
ブロックは正常に終了します。したがって、次に成功した.then
ハンドラーが呼び出されます。
以下の例では、 .catch
を使用した別の状況が示されています。ハンドラー(*)
はエラーをキャッチしますが、それを処理できない (たとえば、 URIError
処理方法しか知らない) ため、再度スローします。
// 実行: catch -> catch new Promise((解決、拒否) => { throw new Error("おっと!"); }).catch(関数(エラー) { // (*) if (URIError のエラーインスタンス) { // 処理してください } それ以外 { alert("そのようなエラーは処理できません"); エラーをスローします。 // このエラーまたは別のエラーをスローすると、次のキャッチにジャンプします } }).then(関数() { /* ここでは実行されません */ }).catch(error => { // (**) alert(`不明なエラーが発生しました: ${error}`); // 何も返さない => 実行は通常通りに行われます });
実行は、最初の.catch
(*)
からチェーンの下の次の .catch (**)
にジャンプします。
エラーが処理されない場合はどうなりますか?たとえば、次のようにチェーンの最後に.catch
を追加するのを忘れました。
新しい Promise(function() { noSuchFunction(); // ここでエラーが発生します (そのような関数はありません) }) .then(() => { // 成功した Promise ハンドラー、1 つ以上 }); // 最後に .catch なし!
エラーが発生した場合、Promise は拒否され、実行は最も近い拒否ハンドラーにジャンプする必要があります。しかし、それはありません。したがって、エラーは「スタック」します。それを処理するコードはありません。
実際には、コード内の通常の未処理エラーと同様に、何かがひどく間違っていることを意味します。
通常のエラーが発生し、 try..catch
でキャッチされなかった場合はどうなりますか?スクリプトが終了し、コンソールにメッセージが表示されます。未処理の約束の拒否でも同様のことが起こります。
JavaScript エンジンはそのような拒否を追跡し、その場合はグローバル エラーを生成します。上記の例を実行すると、コンソールでそれを確認できます。
ブラウザでは、イベントunhandledrejection
使用してこのようなエラーをキャッチできます。
window.addEventListener('unhandledrejection', function(event) { // イベント オブジェクトには 2 つの特別なプロパティがあります。 アラート(イベント.プロミス); // [オブジェクト Promise] - エラーを生成した Promise アラート(event.reason); // エラー: おっと! - 未処理のエラー オブジェクト }); 新しい Promise(function() { throw new Error("おっと!"); }); // エラーを処理するキャッチがありません
イベントは HTML 標準の一部です。
エラーが発生し、 .catch
がない場合は、 unhandledrejection
ハンドラーがトリガーされ、エラーに関する情報を含むevent
オブジェクトを取得するので、何かを行うことができます。
通常、このようなエラーは回復不可能であるため、最善の解決策は、ユーザーに問題について通知し、おそらくそのインシデントをサーバーに報告することです。
Node.js のような非ブラウザ環境では、未処理のエラーを追跡する別の方法があります。
.catch
、 reject()
呼び出しであれ、ハンドラーでスローされたエラーであれ、あらゆる種類の Promise のエラーを処理します。
.then
、2 番目の引数 (エラー ハンドラー) が指定されている場合、同じ方法でエラーもキャッチします。
エラーを処理し、その処理方法を知りたい場所に.catch
正確に配置する必要があります。ハンドラーはエラーを分析し (カスタム エラー クラスが役立ちます)、不明なエラー (プログラミング上の間違いである可能性があります) を再スローする必要があります。
エラーから回復する方法がない場合は、 .catch
まったく使用しなくても大丈夫です。
いずれの場合も、アプリが「そのまま終了」しないように、未unhandledrejection
のエラーを追跡し、それをユーザー (およびおそらくサーバー) に通知するために、(ブラウザー用、および他の環境用の類似した) unhandledrejection イベント ハンドラーを用意する必要があります。
どう思いますか? .catch
トリガーされますか?答えを説明してください。
new Promise(function(解決、拒否) { setTimeout(() => { throw new Error("おっと!"); }, 1000); }).catch(アラート);
答えは「いいえ、そんなことはありません」です。
new Promise(function(解決、拒否) { setTimeout(() => { throw new Error("おっと!"); }, 1000); }).catch(アラート);
この章で述べたように、関数コードの周囲には「暗黙的なtry..catch
」があります。したがって、すべての同期エラーが処理されます。
ただし、ここではエラーはエグゼキュータの実行中ではなく、後で生成されます。したがって、約束では対応できません。