コンテンツにスキップ

エラー処理

はじめに

本サイトにつきまして、【IT技術の知見】はじめに - 俺の技術ノート をご認識のほど宜しくお願いいたします。


01. エラーハンドリング

エラーハンドリングとは

Typescript では throw は、Error オブジェクトだけでなく、どんな型でも返却できる。

ちなみに、よくある型は Error オブジェクト型、object 型、string 型である。

これを catch する場合、さまざまな型を考慮する必要がある。


まずは緩い型で catch する

▼ unknown

unknown を使用した場合、それ以降の処理では型を無視して処理する。

function foo(): string | unknown {
  try {
    // 何らかの処理

    return "success";
  } catch (error: unknown) {
    if (error instanceof Error) {
      // unknownを使用したため、Error型として暗黙的に処理される
      console.error(error.message);
    } else {
      // unknownを使用したため、Error型以外の型として暗黙的に処理される
      console.error("Unknown error:", error);
    }
    return error;
  }
}

▼ any

any を使用した場合、それ以降の処理では型を無視して処理する。

function foo(): string | any {
  try {
    // 何らかの処理

    return "success";
  } catch (error: any) {
    if (error instanceof Error) {
      // anyを使用したため、Error型ではなく型無しとして暗黙的に処理される
      console.error(error.message);
    } else {
      // anyを使用したため、型無しとして暗黙的に処理される
      console.error("Unknown error:", error);
    }
    return error;
  }
}


catch した型に応じた処理

▼ catch した型に応じた処理とは

TypeScript の try-catch で捕捉したエラーの型に応じて処理を実行し分ける場合、catch ブロックを複数書くのではなく、if 文を使用する必要がある。

import axios, {AxiosError} from "axios";

export async function fetchUser(id: string) {
  try {
    const response = await axios.get(`/api/users/${id}`);
    return response.data;
  } catch (error: unknown) {
    // axiosが投げたエラーの場合
    if (axios.isAxiosError(error)) {
      throw new Error(
        `通信エラーが発生しました (${error.response?.status ?? "unknown"})`,
      );
    }

    // 通常のErrorオブジェクトの場合
    if (error instanceof Error) {
      throw new Error(`予期しないエラー: ${error.message}`);
    }

    // その他(string, object, nullなど)の場合
    throw new Error("不明なエラーが発生しました");
  }
}

▼ エラーメッセージの取得

TypeScript では、エラーの構造がさまざまある。

型安全のために、これらを条件分岐で処置する必要がある。

// Remixの場合
import {Response} from "@remix-run/node";

/**
 * エラーの構造に応じてエラーメッセージを取得する
 */
export async function getErrorMessage(error: unknown): Promise<string> {
  // RemixのResponseオブジェクトの場合
  // 例:スローしたjson関数によるエラーを捕捉した場合
  if (error instanceof Response) {
    try {
      const body = await error.json();
      if (typeof body?.message === "string") {
        return body.message;
      }
      if (typeof body?.error === "string") {
        return body.error;
      }
      if (error.statusText) {
        return error.statusText;
      }
      return "An unexpected error occurred.";
    } catch {
      if (error.statusText) {
        return error.statusText;
      }
      return "An unexpected error occurred.";
    }
  }

  // Typescript組み込みのErrorオブジェクトの場合
  // 例:Remixの内部的なエラーを捕捉した場合
  if (error instanceof Error) {
    if (error.message) {
      return error.message;
    }
    return "An unexpected error occurred.";
  }

  // その他の場合
  // 例:null、string、number、objectなどの想定外のエラーを捕捉した場合
  return String(error);
}
// Remixの場合
export const action = async ({request, params}: ActionArgs) => {
  try {
    // リクエストハンドリング
  } catch (error) {
    const errorMessage = await getErrorMessage(error);
    console.error("An error occurred:", errorMessage);
  }
};

▼ ステータスコードの取得

/**
 * エラーの構造に応じてHTTPステータスコードを取得する
 */
export function getErrorStatusCode(error: unknown): number {
  // RemixのResponseオブジェクトの場合
  // 例:スローしたjson関数によるエラーを捕捉した場合
  if (error instanceof Response) {
    return error.status;
  }

  // ZodによるエラーやJSONパースエラーを補足した場合
  if (error instanceof z.ZodError || error instanceof SyntaxError) {
    return 400;
  }

  // どのようなエラーの型が来るかわからないため、よくある構造で決め打ちする
  const errorObject = error as {
    response?: {status?: unknown};
    status?: unknown;
  };

  // AxiosErrorなどのエラーを補足した場合
  if (typeof errorObject.response?.status === "number") {
    return errorObject.response.status;
  }

  // 独自Errorなどのエラーを補足した場合
  if (typeof errorObject.status === "number") {
    return errorObject.status;
  }

  // 想定外のエラーを補足した場合
  return 500;
}