こんにちは、ブロックチェーンチームのエンジニアid:charinesです。
この記事ではJavaScriptにおける unhandledrejection
がどのような条件で発生するのかをクイズ形式でまとめています。
unhandledrejection とは
unhandledrejection
はエラーハンドリングされていない Promise
が拒否されたときにグローバルスコープに送られるイベントです。
unhandledrejection
が発生したときの挙動は環境によって異なりますが、例えばNodeJSではプロセスが強制終了するため、気づかぬうちに発生させないよう注意が必要です。
では具体的にどのような状況で unhandledrejection
が発生するのかをクイズ形式で見ていきます。
クイズ
次のコードを実行したとき unhandledrejection
は発生するでしょうか。
(各問題のコードはNode v18.12.1にて検証しています)
問1
Promise.reject();
解答・解説
発生する
Promise.reject
は拒否された Promise
を返す関数です。エラーハンドラがないため unhandledrejection
が発生します。
問2
Promise.reject() .catch(() => {});
解答・解説
発生しない
Promise.prototype.catch
はエラーハンドラを設定する一般的な方法です。今回は空の関数を渡しているため、式全体は undefined
に解決します。
問3
Promise.reject()
.then(v => v)
.then(v => v, () => {});
解答・解説
発生しない
Promise.prototype.then
の第二引数は Promise.prototype.catch
でエラーハンドラを設定するのと同じ働きをします。
また、ハンドラはPromiseチェーンを辿って呼び出されます。
問4
const f = async () => { const p = Promise.reject(); await sleep(); p.catch(() => {}); }; f();
※ sleep
は一定時間後に解決する Promise
を返す関数で実装は以下です。
import { setTimeout } from "timers/promises"; const sleep = () => setTimeout(50);
解答・解説
発生する
4行目でエラーハンドラを設定していますが、2行目の時点で処理が開始されるため、 p
はエラーハンドラの設定より先に拒否されてしまいます。
ハンドラは Promise
の作成直後に設定するべきです。
問5
const f = async () => { await Promise.reject(); }; f().catch(() => {});
解答・解説
発生しない
await
で待った Promise
が拒否された場合、 await
式はその値で例外を発生させ、 f()
が返す Promise
は拒否されることになります。4行目で f()
に対してハンドラを設定してるため、この場合 unhandledrejection
は発生しません。
2行目の await
がない場合は問1と同じ状況なので unhandledrejection
が発生します。
問6
const f = async () => { const p = Promise.reject().catch(e => { throw e }); await sleep(); await p; }; f().catch(() => {});
解答・解説
発生する
エラーハンドラの中で例外を投げた場合、 catch
はさらに拒否される Promise
を返します。
また、4行目で await
をしていますが、それより前に p
は拒否されてしまうので unhandledrejection
が発生します。
問7
const f = async () => { const p = Promise.reject().catch(e => e); await sleep(); throw await p; }; f().catch(() => {});
解答・解説
発生しない
エラーハンドラによって p
は拒否されずエラーの値に解決します。
非同期関数内で例外を投げる場合は、問5の await
で待った Promise
が拒否されるパターンと同じで、 f()
の返す Promise
が拒否されます。これは6行目でエラーハンドラが設定されているので unhandledrejection
は発生しません。
最後に
7問の Promise
を使った簡単な実装を用いて unhandledrejection
が発生する条件をまとめてみました。
特に問6のように拒否され得る Promise
を作成して後から await
するというパターンで unhandlerejection
が発生してしまうようなケースは注意すべきだと思います。
参考文献
- PromiseのUnhandled Rejectionを完全に理解する
- 日本語の記事でECMAScriptの仕様を読み解いています