こんにちは、ブロックチェーンチームのエンジニア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の仕様を読み解いています