JavaScriptでカスタムエラーを作成する方法
エラーをキャッチすること自体は簡単です。しかし、どのエラーをキャッチしたのか、そしてなぜそれが発生したのかを把握することが難しいのです。catchブロックでデータベースのタイムアウトと不正なユーザー入力を同じように処理していると、デバッグは当て推量になってしまいます。
JavaScriptのカスタムエラーがこの問題を解決します。カスタムエラーを使用すると、アプリケーション全体で意味のあるコンテキストを持つ、構造化された識別可能なエラータイプを作成できます。本ガイドでは、エラーチェーンのためのError.causeや、現在のブラウザとランタイムで確実に動作するJavaScriptエラークラスなど、ES2022+の機能を使用した最新のアプローチを解説します。
重要なポイント
class ... extends Error構文を使用してネイティブのErrorクラスを拡張し、クリーンで保守性の高いカスタムエラーを作成する- スタックトレースを保持しエラーチェーンを有効にするため、
super()にmessageとoptionsの両方を渡す Error.cause(ES2022+)を使用して、外部ソースからの元のエラーをラップして保持する- メッセージ文字列にコンテキストをエンコードする代わりに、
statusCodeやfieldなどの構造化されたフィールドを追加する - エラー階層は浅く保つ—基本エラーといくつかの特化したサブタイプでほとんどのニーズをカバーできる
標準パターン: Errorの拡張
JavaScriptにおける最新のエラーハンドリングは、クラス構文から始まります。レガシーなプロトタイプ操作は忘れてください—class ... extends Errorが基本的なアプローチです:
class ValidationError extends Error {
constructor(message, options) {
super(message, options)
this.name = 'ValidationError'
}
}
super(message, options)の呼び出しは重要です。これはメッセージとoptionsオブジェクトの両方を親のErrorコンストラクタに渡し、スタックトレースのキャプチャを自動的に処理します。
this.nameを設定することで、カスタムエラーがスタックトレースやログで正しく識別されるようになります。これがないと、カスタムエラーは汎用的な「Error」として表示されます。
構造化されたフィールドの追加
単純なメッセージ文字列は脆弱です。「Invalid email: user@」を解析してコンテキストを抽出するのはエラーが発生しやすい方法です。代わりに、構造化されたフィールドを追加します:
class HttpError extends Error {
constructor(message, statusCode, options) {
super(message, options)
this.name = 'HttpError'
this.statusCode = statusCode
}
}
throw new HttpError('Resource not found', 404)
これで、エラーハンドラは文字列を解析する代わりにerror.statusCodeを直接チェックできます。このパターンはきれいにスケールします—details、code、またはアプリケーションが必要とするドメイン固有のフィールドを追加できます。
Error.causeによるエラーチェーン
データベース、API、ライブラリなどの外部ソースからのエラーをラップする場合、元のエラーを保持する必要があります。ES2022で導入されたcauseオプションがこれを処理します:
async function fetchUser(id) {
try {
const response = await fetch(`/api/users/${id}`)
if (!response.ok) {
throw new HttpError('User fetch failed', response.status)
}
return response.json()
} catch (error) {
throw new HttpError('Unable to load user', 500, { cause: error })
}
}
元のエラーはerror.causeを介してアクセス可能なまま残り、デバッグのための完全なエラーチェーンが保持されます。これは最新のブラウザとNode.jsでサポートされており、ポリフィルは不要です。
Discover how at OpenReplay.com.
小規模なエラー階層の構築
JavaScriptのエラー階層は、浅く保つのが最も効果的です。基本的なアプリケーションエラーといくつかの特化したサブタイプで、ほとんどのニーズをカバーできます:
class AppError extends Error {
constructor(message, options = {}) {
super(message, { cause: options.cause })
this.name = this.constructor.name
this.statusCode = options.statusCode ?? 500
}
}
class NotFoundError extends AppError {
constructor(message, options = {}) {
super(message, { ...options, statusCode: 404 })
}
}
class ValidationError extends AppError {
constructor(message, field, options = {}) {
super(message, { ...options, statusCode: 400 })
this.field = field
}
}
this.constructor.nameを使用すると、クラスからエラー名が自動的に設定され、サブクラスでのボイラープレートコードが削減されます。
非同期フローでのエラーの識別
カスタムエラーは、異なる失敗モードを処理する必要がある非同期コードで特に有効です:
try {
const user = await fetchUser(userId)
} catch (error) {
if (error instanceof NotFoundError) {
return { status: 404, body: { message: 'User not found' } }
}
if (error instanceof ValidationError) {
return { status: 400, body: { message: error.message, field: error.field } }
}
// 予期しないエラー—ログに記録して汎用的なレスポンスを返す
console.error('Unexpected error:', error.cause ?? error)
return { status: 500, body: { message: 'Internal error' } }
}
instanceofチェックは継承チェーンを通じて正しく動作します。シリアライズされたエラーを扱う場合は、カスタムフィールドやerror.nameをチェックして識別することもできます。
複数の同時失敗を含むシナリオでは、AggregateErrorがエラーをまとめてバンドルする標準的な方法を提供します—複数の操作が独立して失敗する可能性がある並列操作に便利です。
TypeScriptに関する考慮事項
TypeScriptを使用している場合は、tsconfig.json内でtarget: "ES2022"以上を設定するか、lib配列に"ES2022"を含めてください。これにより、ErrorOptionsインターフェースのcauseプロパティの適切な型付けが保証されます。
まとめ
class ... extends Errorで構築されたJavaScriptのカスタムエラーは、アプリケーション全体でコンテキストを持つ、構造化された識別可能な例外を提供します。super()にmessageとoptionsを渡し、エラーチェーンにはcauseを使用し、メッセージ文字列に情報をエンコードする代わりにドメイン固有のフィールドを追加してください。階層を浅く保つことで、エラーハンドリングが予測可能でデバッグしやすくなります。
よくある質問
カスタムエラーを使用すると、プログラム的に異なる失敗タイプを区別できます。エラーメッセージを解析して何が問題だったかを判断する代わりに、instanceofチェックを使用したり、statusCodeやfieldなどのカスタムプロパティを検査したりできます。これによりエラーハンドリングがより信頼性が高くなり、コードのデバッグと保守が容易になります。
Error.causeはES2022で導入され、すべての最新ブラウザとNode.js 16.9+でサポートされています。古い環境では、causeオプションは単に無視されます—コードは引き続き動作しますが、causeプロパティはundefinedになります。レガシーランタイムをサポートする必要がある場合のみ、ポリフィルを検討してください。
浅く保つべきです—通常は最大2レベルです。単一の基本アプリケーションエラークラスと少数の特化したサブタイプで、ほとんどのユースケースをカバーできます。深い階層は比例した利益なしに複雑さを追加し、アプリケーションの進化に伴うリファクタリングを困難にします。
はい、できます。複数の操作が独立して失敗する可能性がある場合、個々の失敗をカスタムエラーでラップし、AggregateErrorを使用してバンドルします。errorsの配列内の各エラーはそのタイプを保持するため、結果を処理する際にinstanceofチェックを引き続き使用できます。
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.