Back

JavaScriptでカスタムエラーを作成する方法

JavaScriptでカスタムエラーを作成する方法

エラーをキャッチすること自体は簡単です。しかし、どのエラーをキャッチしたのか、そしてなぜそれが発生したのかを把握することが難しいのです。catchブロックでデータベースのタイムアウトと不正なユーザー入力を同じように処理していると、デバッグは当て推量になってしまいます。

JavaScriptのカスタムエラーがこの問題を解決します。カスタムエラーを使用すると、アプリケーション全体で意味のあるコンテキストを持つ、構造化された識別可能なエラータイプを作成できます。本ガイドでは、エラーチェーンのためのError.causeや、現在のブラウザとランタイムで確実に動作するJavaScriptエラークラスなど、ES2022+の機能を使用した最新のアプローチを解説します。

重要なポイント

  • class ... extends Error構文を使用してネイティブのErrorクラスを拡張し、クリーンで保守性の高いカスタムエラーを作成する
  • スタックトレースを保持しエラーチェーンを有効にするため、super()messageoptionsの両方を渡す
  • Error.cause(ES2022+)を使用して、外部ソースからの元のエラーをラップして保持する
  • メッセージ文字列にコンテキストをエンコードする代わりに、statusCodefieldなどの構造化されたフィールドを追加する
  • エラー階層は浅く保つ—基本エラーといくつかの特化したサブタイプでほとんどのニーズをカバーできる

標準パターン: 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を直接チェックできます。このパターンはきれいにスケールします—detailscode、またはアプリケーションが必要とするドメイン固有のフィールドを追加できます。

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でサポートされており、ポリフィルは不要です。

小規模なエラー階層の構築

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()messageoptionsを渡し、エラーチェーンには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.

OpenReplay