カスタムエラークラスを作る|JavaScript

組み込みのエラー型(TypeError、RangeError など)だけでは、アプリケーション固有のエラーを表現しきれないことがあります。そんなときはカスタムエラークラスを作成して、より意味のあるエラーハンドリングを実現しましょう。

基本的なカスタムエラー

Error クラスを継承して独自のエラークラスを作ります。

class ValidationError extends Error {
  constructor(message) {
    super(message);
    this.name = 'ValidationError';
  }
}

throw new ValidationError('入力値が不正です');

super(message) で親クラスの Error を初期化し、this.name でエラー名を設定します。

なぜ this.name を設定するのか

this.name を設定しないと、エラー名が親クラスの “Error” のままになってしまいます。

name を設定しない場合

Error: 入力値が不正です

name を設定した場合

ValidationError: 入力値が不正です

デバッグ時にエラーの種類がひと目でわかるようになります。

追加のプロパティを持たせる

カスタムエラーには独自のプロパティを追加できます。

class HttpError extends Error {
  constructor(statusCode, message) {
    super(message);
    this.name = 'HttpError';
    this.statusCode = statusCode;
  }
}

try {
  throw new HttpError(404, 'ページが見つかりません');
} catch (error) {
  console.log(error.name);       // "HttpError"
  console.log(error.statusCode); // 404
  console.log(error.message);    // "ページが見つかりません"
}

API のエラーレスポンスを扱う際などに便利です。

実用的なカスタムエラーの例

アプリケーションでよく使われるカスタムエラーの例を見てみましょう。

// 認証エラー
class AuthenticationError extends Error {
  constructor(message = '認証に失敗しました') {
    super(message);
    this.name = 'AuthenticationError';
  }
}

// 権限エラー
class AuthorizationError extends Error {
  constructor(message = 'この操作を行う権限がありません') {
    super(message);
    this.name = 'AuthorizationError';
  }
}

// バリデーションエラー(フィールド情報付き)
class FieldValidationError extends Error {
  constructor(field, message) {
    super(message);
    this.name = 'FieldValidationError';
    this.field = field;
  }
}

カスタムエラーを使った分岐処理

instanceof でカスタムエラーの種類を判定し、適切な処理を行えます。

async function fetchUser(id) {
  try {
    const response = await api.getUser(id);
    return response;
  } catch (error) {
    if (error instanceof AuthenticationError) {
      redirectToLogin();
    } else if (error instanceof AuthorizationError) {
      showPermissionDenied();
    } else if (error instanceof HttpError && error.statusCode === 404) {
      showUserNotFound();
    } else {
      showGenericError(error);
    }
  }
}

エラー階層を作る

カスタムエラー同士で継承関係を作ることも可能です。

class AppError extends Error {
  constructor(message) {
    super(message);
    this.name = this.constructor.name;
  }
}

class NetworkError extends AppError {}
class TimeoutError extends NetworkError {}
class ConnectionError extends NetworkError {}

this.constructor.name を使うと、継承先でも自動的に正しいクラス名が name にセットされます。

const timeout = new TimeoutError('接続がタイムアウトしました');
console.log(timeout instanceof TimeoutError); // true
console.log(timeout instanceof NetworkError); // true
console.log(timeout instanceof AppError);     // true
console.log(timeout instanceof Error);        // true

ES5 環境での注意点

ES6 のクラス構文が使えない環境では、prototype チェーンを手動で設定する必要があります。現在のモダンブラウザや Node.js では気にする必要はありませんが、古い環境をサポートする場合は Babel などのトランスパイラを使いましょう。