【TypeScript】非同期処理と例外処理 - 型安全な実装

【TypeScript】非同期処理と例外処理 - 型安全な実装

2024-11-10

2024-11-10

概要

TypeScriptでは、非同期処理や例外処理を型安全に扱うことで、エラー発生時に発生する問題を最小限に抑え、予測可能なコード設計が可能です。本記事では、async/awaitを用いた非同期処理の型安全な実装方法と、例外処理でのエラーハンドリングのベストプラクティスを紹介します。これにより、非同期処理のエラーを確実にキャッチし、安全に処理できるコードを目指します。

非同期処理の基礎

TypeScriptでは、非同期処理を扱うためにPromiseasync/awaitが使用されます。これにより、ネットワークリクエストやファイルの読み書きといった時間のかかる操作を非同期に実行し、他の処理をブロックせずに進めることができます。

async function fetchData(url: string): Promise<string> {
  const response = await fetch(url);
  const data = await response.text();
  return data;
}

このコードでは、指定したURLからデータを取得してテキスト形式で返します。TypeScriptの型システムがPromise<string>の戻り値の型を明示しているため、呼び出し元で戻り値の型を確実に知ることができます。

型安全な例外処理の実装

非同期処理にはエラーがつきものですが、TypeScriptでは型を活用して例外処理をより安全に行うことが可能です。

async/awaitとtry/catchによるエラーハンドリング

非同期関数内でエラーが発生した場合、try/catchブロックを使うことで例外をキャッチし、安全に処理できます。

async function fetchData(url: string): Promise<string | null> {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error("Failed to fetch data");
    }
    return await response.text();
  } catch (error) {
    console.error("Fetch error:", error);
    return null;
  }
}

この例では、エラーが発生した場合にnullを返すことで、呼び出し側でエラー発生時の対策をとりやすくしています。console.errorでエラーメッセージを記録することで、開発中のデバッグにも役立ちます。

エラー型を定義してエラーハンドリングを明確化する

TypeScriptでは、カスタムエラー型を定義してエラーの型安全性を高めることができます。

interface FetchError {
  message: string;
  code: number;
}
async function fetchData(url: string): Promise<string | FetchError> {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      return { message: "Failed to fetch data", code: response.status };
    }
    return await response.text();
  } catch (error) {
    return { message: "Network error", code: 500 };
  }
}
async function main() {
  const result = await fetchData("https://example.com");
  if (typeof result === "string") {
    console.log("Data:", result);
  } else {
    console.error("Error:", result.message);
  }
}
main();

この例では、FetchErrorインターフェースを作成し、エラーの詳細情報を返しています。呼び出し元ではtypeofを使って返り値が文字列かエラーオブジェクトかを判定することで、型安全にエラー処理ができます。

Result型を使ったエラーハンドリング

Result型は、非同期処理の結果が成功か失敗かを表す方法で、関数の戻り値にエラー情報を含められるため、安全なエラーハンドリングが可能です。

Result型の定義

まず、成功と失敗のケースを定義したResult型を作成します。

type Success<T> = {
  success: true;
  data: T;
};
type Failure = {
  success: false;
  error: string;
};
type Result<T> = Success<T> | Failure;

Successはデータが正常に取得された場合を表し、Failureはエラーが発生した場合を表します。これにより、非同期関数の戻り値にエラー情報を含め、呼び出し元で型安全にエラーチェックができます。

Result型を使った実装例

Result型を活用して、データ取得処理を実装してみましょう。

async function fetchData(url: string): Promise<Result<string>> {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      return { success: false, error: "Failed to fetch data" };
    }
    const data = await response.text();
    return { success: true, data };
  } catch (error) {
    return { success: false, error: "Network error" };
  }
}
async function main() {
  const result = await fetchData("https://example.com");
  if (result.success) {
    console.log("Data:", result.data);
  } else {
    console.error("Error:", result.error);
  }
}
main();

このコードでは、データ取得が成功した場合は{ success: true, data }、エラーが発生した場合は{ success: false, error }を返すことで、呼び出し元で型に基づいてエラーハンドリングが可能です。非同期処理の成功と失敗のケースを明確に扱えるため、堅牢で読みやすいコードを実現できます。

非同期エラーハンドリングでのベストプラクティス

  1. 常にtry/catchを使う
    非同期関数には予期せぬエラーがつきものです。try/catchブロックを活用し、エラーが発生した際に対処できるようにしましょう。
  2. エラーの型を定義する
    カスタムエラー型を用いて、エラーの構造や情報を詳細に管理します。これにより、特定のエラーに対する処理が簡単になります。
  3. Result型の利用を検討する
    成功と失敗を表現するResult型を使用することで、関数が返すデータの安全性と予測可能性が向上します。
  4. 非同期エラーのロギング
    エラーハンドリングで必ずエラーメッセージを記録し、問題発生時のトラブルシューティングを行いやすくします。

まとめ

TypeScriptの非同期処理と例外処理において、型安全な実装はコードの信頼性と保守性を大幅に向上させます。try /catchを基本としたasync/awaitのエラーハンドリングに加え、Result型を用いた成功・失敗の明確化や、カスタムエラー型の定義など、実践的なテクニックを取り入れることで、安全で堅牢な非同期処理が実現できます。TypeScriptの型システムを活かし、エラーハンドリングのベストプラクティスを適用して、型安全なコードを目指しましょう。

Recommend