Mobile Factory Tech Blog

技術好きな方へ!モバイルファクトリーのエンジニアたちが楽しい技術話をお届けします!

JavaScript: unhandledrejectionの発生条件クイズ!

こんにちは、ブロックチェーンチームのエンジニアid:charinesです。

この記事ではJavaScriptにおける unhandledrejection がどのような条件で発生するのかをクイズ形式でまとめています。

unhandledrejection とは

unhandledrejection はエラーハンドリングされていない Promise が拒否されたときにグローバルスコープに送られるイベントです。

developer.mozilla.org

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 が発生してしまうようなケースは注意すべきだと思います。

参考文献