非同期処理のエラーハンドリングは try-catch で書くことが多いが、例外が発生する場所とハンドリングする場所が離れてしまいがちだ。Result 型パターンを使うと、戻り値として成功と失敗を明示的に分けて扱える。
前提:Error 型への変換
unknown 型のエラーを確実に Error 型に変換するヘルパー。
export function toError(error: unknown): Error { if (error instanceof Error) return error; return new Error(error as never);}Result 型の定義
interface Failure { data: undefined; error: Error;}
interface Success<T> { data: T; error: undefined;}
type Result<T> = Success<T> | Failure;fetch ラッパーの実装例
import { toError } from './toError.js'
export async function fetcher<T>( url: RequestInfo, init?: RequestInit): Promise<Result<T>> { const UNDEF = undefined;
// fetch が失敗した場合(ネットワークエラーなど) const response = await fetch(url, init).catch(toError); if (response instanceof Error) { return { data: UNDEF, error: response }; }
// HTTP エラーレスポンス if (!response.ok) { return { data: UNDEF, error: new Error(`HTTP ${response.status}: ${response.statusText}`) }; }
// 成功 const data = await response.json(); return { data, error: UNDEF };}使用例
const result = await fetcher<User>("/api/user/123");
if (result.error) { console.error("取得失敗:", result.error.message); return;}
// result.error が undefined の場合、TypeScript は result.data を T として推論console.log(result.data.name);メリットと注意点
メリット:
- 成功と失敗が戻り値の型で明確になる
- try-catch ブロックが減り可読性が向上する
- エラーハンドリングを強制できる(省略すると型エラー)
注意点:
- ECMAScript の Safe Assignment Operator プロポーザルが標準化されると、組み込みで同様の機能が使えるようになるかもしれない
- Go や Rust の言語組み込みの Result 型ほど完備していない
- バリデーションエラーなど詳細なエラーハンドリングは別途実装が必要
実装は手抜きだが、小規模なプロジェクトで型安全にエラーを扱いたい時に有効だ。
hsb.horse