unhandledrejection で未処理エラーを検知|JavaScript

Promise のエラーを catch し忘れると、エラーがどこにも伝わらず、バグの発見が遅れることがあります。unhandledrejection イベントを使えば、未処理の Promise エラーを一括で検知できます。

未処理のリジェクションとは

Promise がリジェクトされたのに、catch で処理されていない状態を指します。

// catch がないため未処理になる
Promise.reject(new Error('処理失敗'));

// async 関数でも同様
async function fail() {
  throw new Error('エラー');
}
fail(); // 呼び出し側で catch していない

ブラウザのコンソールには「Uncaught (in promise)」と表示されますが、これだけでは見落としがちです。

unhandledrejection イベント

window に unhandledrejection イベントリスナーを登録すると、未処理のリジェクションをキャッチできます。

window.addEventListener('unhandledrejection', event => {
  console.error('未処理の Promise エラー:', event.reason);
});

// これで検知される
Promise.reject(new Error('テストエラー'));

event.reason にはリジェクトされた理由(通常は Error オブジェクト)が入っています。

イベントオブジェクトのプロパティ

reasonリジェクトの理由(Error など)
promiseリジェクトされた Promise オブジェクト
window.addEventListener('unhandledrejection', event => {
  console.log('reason:', event.reason);
  console.log('promise:', event.promise);
});

エラーの伝播を止める

preventDefault() を呼ぶと、コンソールへのデフォルトのエラー出力を抑制できます。

window.addEventListener('unhandledrejection', event => {
  event.preventDefault(); // コンソールへの出力を抑制
  
  // 独自のエラーログ処理
  sendToErrorTracker({
    message: event.reason?.message,
    stack: event.reason?.stack
  });
});

ただし、開発中はデフォルトの出力があった方がデバッグしやすいため、本番環境でのみ抑制するのが一般的です。

rejectionhandled イベント

一度 unhandledrejection として検知されたエラーが、後から catch されると rejectionhandled イベントが発火します。

window.addEventListener('rejectionhandled', event => {
  console.log('後から処理されました:', event.reason);
});

const p = Promise.reject(new Error('遅延エラー'));
// unhandledrejection が発火

setTimeout(() => {
  p.catch(() => {}); // rejectionhandled が発火
}, 1000);

この挙動を理解しておくと、エラー追跡の際に混乱を避けられます。

実践的なエラー追跡

本番環境では、未処理エラーを外部のエラー追跡サービスに送信することが多いです。

window.addEventListener('unhandledrejection', event => {
  const error = event.reason;
  
  // エラー追跡サービスに送信
  errorTracker.captureException(error, {
    extra: {
      type: 'unhandledrejection',
      url: location.href
    }
  });
});

Sentry や Bugsnag などのサービスは、この設定を自動で行ってくれます。

Node.js での unhandledRejection

Node.js では process オブジェクトで同様のイベントを監視できます。

process.on('unhandledRejection', (reason, promise) => {
  console.error('未処理の Promise リジェクション:', reason);
});

Node.js 15 以降では、未処理のリジェクションがあるとプロセスがクラッシュするようになりました。必ずエラー処理を行うか、このイベントで捕捉しましょう。

開発中の活用

開発中はすべての未処理リジェクションを目立たせると、catch 漏れを早期に発見できます。

if (process.env.NODE_ENV === 'development') {
  window.addEventListener('unhandledrejection', event => {
    console.error('⚠️ 未処理の Promise エラーを検知!');
    console.error('詳細:', event.reason);
    console.error('このエラーを適切に catch してください');
  });
}

理想的には、このイベントが発火しないようにコードを書くことが目標です。すべての Promise には catch(または try-catch)を付けることを習慣にしましょう。