TypeScriptで押さえるべき非同期処理のエッセンス
フロントエンド/バックエンド問わず、非同期処理はあらゆるアプリケーションの基盤である。TypeScript では型安全性を担保しつつ、JavaScript と同等の記述性で非同期を扱える。本稿では「コールバック地獄 → Promise → async/await」という歴史的背景を踏まえ、実務で頻出のパターンに絞って要点を整理する。
1. Promise と async/await の役割分担
1.1 Promise で型安全な状態管理
const fetchUser = (id: string): Promise<User> =>
fetch(`/api/users/${id}`)
.then(res => res.json() as Promise<User>);
-
Promise<T>は「まだ値が無いが、将来 T が得られる」ことを表す型。 - 状態は pending → fulfilled/rejected の 3 すくみ。型で把握できるメリットは大きい。
1.2 async/await でフローを直列化
async function getUserName(id: string): Promise<string> {
const user = await fetchUser(id); // User 型が保持される
return user.name;
}
-
awaitにより then チェーンの分割が不要になり、読みやすさ向上。 - 非同期関数は常に
Promise<戻り値の型>を返す点に注意。
2. 並行処理と依存関係
2.1 Promise.all で集合的エラー検知
const [profile, posts] = await Promise.all([
fetchUserProfile(id),
fetchUserPosts(id),
]);
- 相互に依存しない呼び出しを 同時実行し、全完了まで待機。
- いずれかが reject すると即座に catch へバブリング。
2.2 逐次依存は for...of + await
for (const id of userIds) {
const user = await fetchUser(id); // 完了順を厳密に保つ
console.log(user.name);
}
-
forEach+asyncでは意図しない並行実行になるため要注意。
3. エラーハンドリングの鉄則
3.1 try/catch と戻り値ラップ
try {
const user = await fetchUser(id);
return { ok: true, data: user } as const;
} catch (error) {
return { ok: false, error } as const;
}
- 例外を制御不能な場所まで投げない設計が、再利用性を高める。
- 戻り値を discriminated union にすると UI 側で型安全に分岐可能。
3.2 予期しない例外を握り潰さない
グローバルで window.addEventListener('unhandledrejection', ...) を仕込み、ログ基盤へ即時転送するのがセオリー。
4. よくある落とし穴
| 罠 | 症状 | 解決策 |
|---|---|---|
| async void 関数 | 返り値を無視して例外が握り潰される | 必ず Promise<unknown> を返す |
| 競合状態 | 前回の非同期呼び出し結果が後から到着 |
AbortController でキャンセル |
| ブロッキング計算 | UI フリーズ | Web Worker でオフロード |
5. まとめ
- Promise は状態管理、async/await は可読性向上――この黄金則を守るだけで、大半のバグは防げる。
- しかし非同期を多用し過ぎると
- スタックトレースが分断されデバッグが困難になる
- マイクロタスクの過剰生成でイベントループを圧迫し、パフォーマンスが逆に低下する
- 依存関係が錯綜し、“どの処理がどこで完了するか” の可視性が失われる
- 隠れた競合状態(race condition)が発見しづらくなる
- 解決策はシンプルである。
- 同期で書ける箇所は同期で書く(I/O が絡まないロジックはまず同期)。
- 非同期フローを ドメイン単位で関数分割し、「入力と出力」の責務を明示する。
- ツールを味方に:TypeScript の型、ESLint の
no-floating-promises、そして各種 APM を駆使し、“非同期の健康診断” を継続的に行う。
非同期はアプリを俊敏にする特効薬である一方、無秩序に投与すれば副作用も大きい。
「本当に await が必要か?」 を自問しながら、適量を守って安全に使いこなそう。