web-dev-qa-db-ja.com

Promise.catchハンドラー内にスローできないのはなぜですか?

キャッチコールバック内にErrorをスローして、プロセスが他のスコープ内にあるかのようにエラーを処理できないのはなぜですか?

console.log(err)を実行しないと、何も出力されず、何が起こったのか何もわかりません。プロセスは終了しました...

例:

function do1() {
    return new Promise(function(resolve, reject) {
        throw new Error('do1');
        setTimeout(resolve, 1000)
    });
}

function do2() {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            reject(new Error('do2'));
        }, 1000)
    });
}

do1().then(do2).catch(function(err) {
    //console.log(err.stack); // This is the only way to see the stack
    throw err; // This does nothing
});

メインスレッドでコールバックが実行される場合、なぜErrorがブラックホールに飲み込まれますか?

113
demian85

他の人が説明したように、「ブラックホール」は、.catchの内部へのスローが拒否されたプロミスでチェーンを継続し、エラーを飲み込む未終了のチェーンにつながるキャッチがないためです(悪い!)

もう1つキャッチを追加して、何が起こっているのかを確認します。

do1().then(do2).catch(function(err) {
    //console.log(err.stack); // This is the only way to see the stack
    throw err; // Where does this go?
}).catch(function(err) {
    console.log(err.stack); // It goes here!
});

チェーンの途中でのキャッチは、失敗したステップにもかかわらずチェーンを続行したい場合に役立ちますが、再スローは、情報のログ記録やクリーンアップなどを行った後にcontinue failingに役立ちますおそらく、どのエラーがスローされるかを変更します。

トリック

最初に意図したとおり、Webコンソールにエラーとしてエラーを表示するには、次のトリックを使用します。

.catch(function(err) { setTimeout(function() { throw err; }); });

行番号も残っているため、Webコンソールのリンクを使用すると、(元の)エラーが発生したファイルと行に直接移動できます。

なぜ機能するのか

約束履行または拒否ハンドラーと呼ばれる関数の例外は、返されるはずの約束の拒否に自動的に変換されます。関数を呼び出すプロミスコードがこれを処理します。

一方、setTimeoutによって呼び出される関数は、常にJavaScriptの安定状態から実行されます。つまり、JavaScriptイベントループの新しいサイクルで実行されます。そこにある例外は何にもとらわれず、Webコンソールに到達します。 errは、元のスタック、ファイル、行番号など、エラーに関するすべての情報を保持しているため、依然として正しく報告されます。

143
jib

ここで理解すべき重要な事項

  1. then関数とcatch関数は両方とも、新しいpromiseオブジェクトを返します。

  2. スローまたは明示的に拒否すると、現在のプロミスが拒否された状態に移行します。

  3. thenおよびcatchは新しいpromiseオブジェクトを返すため、これらをチェーン化できます。

  4. Promiseハンドラー(thenまたはcatch)内でスローまたは拒否した場合、チェーンパスの次の拒否ハンドラーで処理されます。

  5. Jfriend00で述べたように、thenおよびcatchハンドラーは同期的に実行されません。ハンドラーがスローすると、すぐに終了します。したがって、スタックは巻き戻され、例外は失われます。そのため、例外をスローすると現在の約束が拒否されます。


あなたの場合、Errorオブジェクトをスローすることでdo1内で拒否しています。これで、現在のプロミスは拒否状態になり、コントロールは次のハンドラー(この場合はthen)に転送されます。

thenハンドラーには拒否ハンドラーがないため、do2はまったく実行されません。これを確認するには、console.logを使用します。現在のプロミスには拒否ハンドラがないため、前のプロミスの拒否値で拒否され、コントロールはcatchである次のハンドラに転送されます。

catchは拒否ハンドラであるため、その中でconsole.log(err.stack);を実行すると、エラースタックトレースを確認できます。現在、そこからErrorオブジェクトをスローしているので、catchによって返されるpromiseも拒否状態になります。

catchに拒否ハンドラをアタッチしていないため、拒否を監視することはできません。


このように、チェーンを分割してこれをよりよく理解できます

var promise = do1().then(do2);

var promise1 = promise.catch(function (err) {
    console.log("Promise", promise);
    throw err;
});

promise1.catch(function (err) {
    console.log("Promise1", promise1);
});

あなたが得る出力は次のようなものになります

Promise Promise { <rejected> [Error: do1] }
Promise1 Promise { <rejected> [Error: do1] }

catchハンドラー1内では、拒否されたpromiseオブジェクトの値を取得しています。

同様に、catchハンドラー1によって返されたプロミスも、promiseが拒否されたのと同じエラーで拒否され、2番目のcatchハンドラーでそれを観察しています。

40
thefourtheye

上記のsetTimeout()メソッドを試しました...

.catch(function(err) { setTimeout(function() { throw err; }); });

迷惑なことに、これは完全にテスト不可能であることがわかりました。非同期エラーがスローされるため、catchはエラーがスローされるまでにリッスンを停止するため、try/catchステートメント内にラップすることはできません。

完全に機能するリスナーを使用するだけに戻りました。これは、JavaScriptの使用方法であるため、非常にテスト可能でした。

return new Promise((resolve, reject) => {
    reject("err");
}).catch(err => {
    this.emit("uncaughtException", err);

    /* Throw so the promise is still rejected for testing */
    throw err;
});
3
RiggerTheGeek

仕様(3.III.dを参照)

d。呼び出して例外eをスローした場合、
a。 resolvePromiseまたはrejectPromiseが呼び出されている場合は、無視してください。
b。それ以外の場合は、eを理由として約束を拒否します。

つまり、then関数で例外をスローすると、キャッチされ、約束が拒否されます。 catchはここでは意味がありません。これは.then(null, function() {})への単なるショートカットです

コードに未処理の拒否を記録する必要があると思います。ほとんどのPromiseライブラリは、unhandledRejectionを起動します。ここに 関連する要点 とそれに関する議論があります。

2
just-boris

私はこれが少し遅いことを知っていますが、私はこのスレッドに出くわしました、そして、どのソリューションも私にとって簡単に実装できなかったので、私は自分のものを思いつきました:

次のように、promiseを返す小さなヘルパー関数を追加しました。

function throw_promise_error (error) {
 return new Promise(function (resolve, reject){
  reject(error)
 })
}

次に、エラーをスローする(およびプロミスを拒否する)約束チェーンの特定の場所がある場合、構築されたエラーを使用して上記の関数から単純に戻ります。

}).then(function (input) {
 if (input === null) {
  let err = {code: 400, reason: 'input provided is null'}
  return throw_promise_error(err)
 } else {
  return noterrorpromise...
 }
}).then(...).catch(function (error) {
 res.status(error.code).send(error.reason);
})

このように、私はプロミスチェーンの内部から余分なエラーを投げることを制御しています。 「通常の」promiseエラーも処理する場合は、キャッチを拡張して「自己スロー」エラーを個別に処理します。

これが役立つことを願って、それが私の最初のstackoverflowの答えです!

1
nuudles

はいは、飲み込みエラーを約束し、他の回答で詳細に説明されているように、.catchでのみキャッチできます。 Node.jsで、通常のthrowの動作を再現し、スタックトレースをコンソールに出力してプロセスを終了する場合は、次のようにします。

...
  throw new Error('My error message');
})
.catch(function (err) {
  console.error(err.stack);
  process.exit(0);
});
0
Jesús Carrera