TypeScriptで非同期処理を扱う際の型設計について、実践的なベストプラクティスを解説します。

Promiseを使った基本的な型付け

非同期処理の戻り値の型は、`Promise`を使って定義します。

// 基本的なPromiseの型定義
function fetchUser(): Promise<User> {
  return fetch('/api/user')
    .then(response => response.json());
}

// インターフェースの定義
interface User {
  id: number;
  name: string;
  email: string;
}

async/awaitを使った型付け

async/awaitを使用する場合も、戻り値の型は`Promise`となります。

async function getUserData(): Promise<User> {
  const response = await fetch('/api/user');
  const user = await response.json();
  return user;
}

エラーハンドリングの型設計

エラーハンドリングを考慮した型設計は以下のように行います。

// カスタムエラーの定義
class APIError extends Error {
  constructor(public statusCode: number, message: string) {
    super(message);
    this.name = 'APIError';
  }
}

// エラーハンドリングを含む非同期関数
async function fetchUserWithError(): Promise<User> {
  try {
    const response = await fetch('/api/user');
    if (!response.ok) {
      throw new APIError(response.status, 'Failed to fetch user');
    }
    return await response.json();
  } catch (error) {
    if (error instanceof APIError) {
      // 型付けされたエラー処理
      console.error(`API Error: ${error.statusCode}`);
    }
    throw error;
  }
}

ジェネリック型を活用した汎用的な型設計

再利用可能な非同期関数の型設計には、ジェネリック型が有効です。

// 汎用的なAPI呼び出し関数
async function apiRequest<T>(url: string): Promise<T> {
  const response = await fetch(url);
  if (!response.ok) {
    throw new APIError(response.status, 'API request failed');
  }
  return response.json();
}

// 使用例
const user = await apiRequest<User>('/api/user');
const posts = await apiRequest<Post[]>('/api/posts');

非同期関数の戻り値の型推論

TypeScriptの型推論を活用することで、より簡潔なコードを書くことができます。

// 型推論を活用した例
const fetchUserData = async () => {
  const response = await fetch('/api/user');
  return response.json() as Promise<User>;
};

// 型アサーションを使用した例
const fetchPosts = async () => {
  const response = await fetch('/api/posts');
  const data = await response.json();
  return data as Post[];
};

Union型を使った結果の型設計

成功と失敗のケースを明示的に型付けする場合は、Union型が有効です。

interface Success<T> {
  success: true;
  data: T;
}

interface Failure {
  success: false;
  error: string;
}

type Result<T> = Success<T> | Failure;

async function fetchData<T>(url: string): Promise<Result<T>> {
  try {
    const response = await fetch(url);
    const data = await response.json();
    return { success: true, data };
  } catch {
    return { success: false, error: 'Failed to fetch data' };
  }
}

以上の型設計パターンを適切に組み合わせることで、型安全で保守性の高い非同期処理を実装できます。実際のプロジェクトでは、要件に応じて最適な型設計を選択することが重要です。

これらの型設計パターンは、TypeScriptの型システムを最大限に活用し、開発時のエラーを早期に発見できるようにします。また、IDEのコード補完機能も効果的に機能し、開発効率の向上にも繋がります。