【JavaScript】堅牢なアプリ開発の鍵!非同期時代のモダンエラーハンドリング戦略
※本ページのリンクにはプロモーションが含まれています。
こんにちは、Ryohei(@ityryohei)です!
堅牢なWebアプリケーションを開発する上で、エラーハンドリング(例外処理)は避けて通れないテーマです。エラーを適切に処理しないと、アプリケーションが突然停止したり、ユーザーに不快な体験を与えたりする原因になります。
モダンなJavaScript開発では、非同期処理(API通信など)が主流となり、エラーの発生源が複雑化しています。単なるtry...catchだけでなく、Promiseやasync/awaitでのエラーを「漏らさず」「正しく」捕捉し、さらにブラウザ全体で未処理のエラーを監視するグローバルハンドリングのテクニックは、バグのないアプリケーションを構築するための必須スキルです。
本記事では、同期・非同期処理におけるエラー捕捉のメカニズムの違いを明確にし、実務で必須となるエラー伝播の設計戦略、そして未捕捉エラーを検出するグローバル監視までを徹底解説します。
非同期処理でエラーが起きたときって、どこでキャッチすればいいの?それとも、ブラウザ全体でまとめて監視する方法があるの?
上記の疑問にお答えします。
1. 基礎知識:同期処理のエラー捕捉メカニズム
エラー処理の基本は、コールスタックを遡る(さかのぼる)仕組みを理解することです。
1.1. try...catchと例外の伝播
同期処理においてtry...catch内でエラー(例外)が発生すると、JavaScriptエンジンはただちに実行を中断し、コールスタックを逆順に辿りながら、エラーを捕捉できる最も近いcatchブロックを探します。
try: エラーが発生する可能性のあるコードを囲みます。catch (error): 例外を捕捉し、エラーオブジェクトを受け取って処理を再開します。finally:tryやcatchの結果にかかわらず、必ず実行されます。(リソースの解放などに使われます。)
1.2. エラーオブジェクト(Error Object)の活用
catchブロックで受け取るerrorオブジェクトは、単なるメッセージではありません。エラーオブジェクトは以下の重要な情報を含んでいます。
- name:エラーの種類(例:
SyntaxError,TypeError,ReferenceError)。 - message:エラーの具体的な説明。
- stack:エラーが発生したコールスタックの履歴。デバッグ時に最も重要な情報です。
2. 非同期処理の壁:エラーハンドリングの「設計戦略」
非同期処理のエラーは、メインのコードフロー(コールスタック)とは別のタイミングで発生するため、同期的なtry...catchでは捕捉できません。Promiseは、この問題を解決するために「失敗」を専門に扱う機構を提供します。
2.1. Promiseの状態遷移と.catch()の役割
Promiseは「成功(Fulfilled)」または「失敗(Rejected)」のどちらか一方に遷移します。エラーハンドリングは、Promiseが失敗状態(Rejected)になったときに発動します。
| 状態 | 意味 | 処理 |
| Pending | 処理中 | 初期状態 |
| Fulfilled | 成功 | .then() で処理 |
| Rejected | 失敗 | .catch() で処理 |
- エラー伝播の原則:
.catch()でエラーを処理した後、returnを省略するか、別のエラーをthrowしない限り、その後の.then()には正常な値(またはundefined)が渡り、チェーンが途切れません。
2.2. async/awaitと同期的なエラー捕捉の復活
async/awaitは、Promiseの失敗を「同期的な例外」として扱うよう抽象化します。
awaitで待機しているPromiseが失敗した場合、その箇所で例外がthrowされたのと同じ挙動になります。- これにより、非同期コードを同期処理と全く同じように
try...catchで囲むことが可能になり、コードがシンプルになります。
JavaScript
async function loadData() {
try {
const data = await fetchData('/api/data');
return data;
} catch (error) {
// 捕捉したエラーをログに記録し、さらに呼び出し元へ再スローする
console.error("データ取得に失敗:", error.message);
throw error; // 呼び出し元で再度キャッチできるようにエラーを伝播させる
}
}
2.3. 並列処理のエラー管理:Promise.all() vs. Promise.allSettled()
実務では複数のAPIを同時に呼び出すことが多いため、エラー処理の戦略的な使い分けが重要です。
| 処理方法 | 目的 | エラー発生時の挙動 | 最適な利用シーン |
Promise.all() | すべてのタスクが成功することを保証 | 一つでも失敗すると全体が即座に失敗(ショートサーキット)し、最初に発生したエラーのみを返す。 | 厳密な依存関係がある、すべてが成功しないと意味がない処理。 |
Promise.allSettled() | すべてのタスクの結果を知りたい | すべてのPromiseが完了(成功/失敗)するのを待つ。個々の結果を配列で返す。 | 必須ではないタスクが含まれる、部分的な失敗を許容する処理。 |
3. 最重要課題:未捕捉エラー(Uncaught Exception)のグローバル監視
Promiseチェーンの最後に.catch()がない、あるいはasync関数内でtry...catchが漏れた場合、エラーはアプリケーションのどこにも捕捉されず、ブラウザ全体に未捕捉エラーとして露出します。
これはWebアプリの最も深刻な問題であり、ユーザー体験の低下やデータの喪失に直結します。
3.1. window.onerrorによる同期エラーの監視
HTMLのDOM操作や同期的なスクリプトで発生した未捕捉エラーは、window.onerrorイベントで監視できます。
JavaScript
window.onerror = function(message, source, lineno, colno, error) {
console.log("【グローバル監視: 同期エラー】", message);
// 外部のエラー追跡サービス(Sentry, Datadogなど)に報告
reportErrorToServer(error);
// trueを返すと、ブラウザコンソールへの標準エラー出力を抑制できる
return true;
};
3.2. unhandledrejectionによる非同期エラーの監視
Promiseチェーンの最後に.catch()がなかったために発生した未捕捉のPromise失敗(非同期エラー)は、window.onunhandledrejectionイベントで監視します。
JavaScript
window.addEventListener('unhandledrejection', (event) => {
// event.reason に Promiseの失敗理由(Errorオブジェクトなど)が含まれる
console.warn("【グローバル監視: 未処理Promise失敗】", event.reason);
// サーバーへ報告する処理
reportErrorToServer(event.reason);
// デフォルトのブラウザ警告を防ぐ
event.preventDefault();
});
この二つのグローバルハンドラをセットで実装することが、アプリケーションのエラーカバレッジ(網羅率)を最大化する最も重要な戦略です。
最後に
エラーハンドリングは、単なるバグ潰しではなく、アプリケーションの堅牢性を設計するプロセスです。
- 原則:すべての非同期操作(Promise、
async関数)には、必ず.catch()またはtry...catchの処理を組み込みましょう。 - 伝播:エラーを処理した後、呼び出し元に問題を知らせる必要がある場合は、必ず
throw errorでエラーを再スロー(再伝播)しましょう。 - グローバル監視:
window.onerrorとunhandledrejectionを利用して、開発者の予測を超えて発生した未捕捉エラーを検知し、改善に役立てましょう。
これらの戦略を身につけることで、あなたのアプリケーションは予期せぬ中断から解放され、ユーザーに信頼される高品質なものになるでしょう。
以上、非同期時代のモダンエラーハンドリング戦略についてのご紹介でした!