async/awaitとエラーハンドリング
学習の目標
本章では、以下の内容を学習します。
- async関数の戻り値の型を理解する
- awaitでの型推論の仕組みを学ぶ
- try-catchでの型安全なエラー処理方法を習得する
- カスタムエラー型の定義方法を理解する
はじめに
前回はPromise型とコールバック関数について学びました。今回は、より読みやすく書きやすい非同期処理の書き方であるasync/await構文について学んでいきましょう。
async/awaitは、Promiseを使った非同期処理をより簡潔に書くための機能です。また、非同期処理では予期しないエラーが発生することがあるため、適切なエラーハンドリングも重要になります。
TypeScriptでは、これらの非同期処理やエラー処理にも型を指定することで、より安全で理解しやすいコードを書くことができます。
それでは、実際にコードを書きながらasync/awaitとエラーハンドリングについて学んでいきましょう。
async関数の戻り値の型
基本的なasync関数
まずは、async関数の基本的な書き方と型指定を見てみましょう。VS Codeで新しくasync-await.tsというファイルを作成し、以下のコードを入力してください。
// async関数の基本的な書き方async function fetchNumber(): Promise<number> { console.log("数値を取得中..."); // 1秒待ってから数値を返す return new Promise((resolve) => { setTimeout(() => { const number = 42; console.log("数値を取得しました:", number); resolve(number); }, 1000); });}
// async関数を実行async function main(): Promise<void> { console.log("処理を開始します"); const result = await fetchNumber(); console.log("結果:", result); console.log("処理が完了しました");}
main();asyncキーワードを付けた関数は、自動的にPromiseを返します。fetchNumber関数はPromise<number>を返し、main関数は何も返さないのでPromise<void>となります。
ファイルをコンパイルして実行すると、以下のような結果が表示されます。
処理を開始します数値を取得中...数値を取得しました: 42結果: 42処理が完了しました複数の非同期処理を順番に実行
async/awaitを使うと、複数の非同期処理を順番に実行することが簡単に書けます。
// 文字列を返すasync関数async function fetchGreeting(name: string): Promise<string> { console.log(`${name}さんの挨拶を準備中...`); return new Promise((resolve) => { setTimeout(() => { const greeting = `こんにちは、${name}さん!`; console.log("挨拶を準備しました:", greeting); resolve(greeting); }, 800); });}
// 複数の非同期処理を順番に実行async function greetMultipleUsers(): Promise<void> { console.log("挨拶処理を開始"); const greeting1 = await fetchGreeting("田中"); console.log("1番目:", greeting1); const greeting2 = await fetchGreeting("佐藤"); console.log("2番目:", greeting2); console.log("すべての挨拶が完了しました");}
greetMultipleUsers();実行すると、以下のような結果が表示されます。
挨拶処理を開始田中さんの挨拶を準備中...挨拶を準備しました: こんにちは、田中さん!1番目: こんにちは、田中さん!佐藤さんの挨拶を準備中...挨拶を準備しました: こんにちは、佐藤さん!2番目: こんにちは、佐藤さん!すべての挨拶が完了しましたawaitでの型推論
型推論の動作確認
TypeScriptは、awaitを使用した際に、Promiseから取り出される値の型を自動的に推論してくれます。
// 異なる型を返すasync関数たちasync function getAge(): Promise<number> { return new Promise((resolve) => { setTimeout(() => resolve(25), 500); });}
async function getName(): Promise<string> { return new Promise((resolve) => { setTimeout(() => resolve("山田太郎"), 500); });}
async function getIsActive(): Promise<boolean> { return new Promise((resolve) => { setTimeout(() => resolve(true), 500); });}
// awaitで型推論を確認async function displayUserInfo(): Promise<void> { // TypeScriptは自動的にnumber型と推論 const age = await getAge(); console.log("年齢:", age, "型:", typeof age);
// TypeScriptは自動的にstring型と推論 const name = await getName(); console.log("名前:", name, "型:", typeof name);
// TypeScriptは自動的にboolean型と推論 const isActive = await getIsActive(); console.log("アクティブ:", isActive, "型:", typeof isActive);}
displayUserInfo();実行すると、以下のような結果が表示されます。
年齢: 25 型: number名前: 山田太郎 型: stringアクティブ: true 型: booleanTypeScriptの型推論により、awaitで取得した値は適切な型として扱われます。
try-catchでの型安全なエラー処理
基本的なエラーハンドリング
非同期処理では、ネットワークエラーやデータの不整合など、様々な理由でエラーが発生する可能性があります。try-catch文を使って、これらのエラーを適切に処理しましょう。
// エラーが発生する可能性があるasync関数async function fetchDataWithError(shouldFail: boolean): Promise<string> { console.log("データを取得中..."); return new Promise((resolve, reject) => { setTimeout(() => { if (shouldFail) { reject(new Error("データの取得に失敗しました")); } else { resolve("データの取得に成功しました"); } }, 1000); });}
// エラーハンドリングを含むasync関数async function safeDataFetch(): Promise<void> { try { console.log("--- 成功パターン ---"); const successData = await fetchDataWithError(false); console.log("成功:", successData);
console.log("--- エラーパターン ---"); const errorData = await fetchDataWithError(true); console.log("この行は実行されません"); } catch (error) { console.log("エラーをキャッチしました:", error instanceof Error ? error.message : error); } console.log("処理を続行します");}
safeDataFetch();実行すると、以下のような結果が表示されます。
--- 成功パターン ---データを取得中...成功: データの取得に成功しました--- エラーパターン ---データを取得中...エラーをキャッチしました: データの取得に失敗しました処理を続行します複数の処理でのエラーハンドリング
複数の非同期処理がある場合のエラーハンドリングも見てみましょう。
// ユーザー情報を取得する関数(エラーの可能性あり)async function fetchUser(userId: number): Promise<object> { console.log(`ユーザー ${userId} の情報を取得中...`); return new Promise((resolve, reject) => { setTimeout(() => { if (userId === 999) { reject(new Error("ユーザーが見つかりません")); } else { resolve({ id: userId, name: `ユーザー${userId}`, email: `user${userId}@example.com` }); } }, 600); });}
// 複数のユーザー情報を安全に取得async function fetchMultipleUsers(): Promise<void> { const userIds = [1, 2, 999, 3]; // 999は存在しないユーザー
for (const userId of userIds) { try { const user = await fetchUser(userId); console.log("取得成功:", user); } catch (error) { console.log(`ユーザー ${userId} の取得でエラー:`, error instanceof Error ? error.message : error); // エラーが発生しても次のユーザーの処理を続ける } } console.log("全ユーザーの処理が完了しました");}
fetchMultipleUsers();実行すると、以下のような結果が表示されます。
ユーザー 1 の情報を取得中...取得成功: {id: 1, name: "ユーザー1", email: "user1@example.com"}ユーザー 2 の情報を取得中...取得成功: {id: 2, name: "ユーザー2", email: "user2@example.com"}ユーザー 999 の情報を取得中...ユーザー 999 の取得でエラー: ユーザーが見つかりませんユーザー 3 の情報を取得中...取得成功: {id: 3, name: "ユーザー3", email: "user3@example.com"}全ユーザーの処理が完了しましたカスタムエラー型の定義
独自のエラークラス
より詳細なエラー情報を扱うために、独自のエラークラスを定義することができます。
// カスタムエラークラスの定義class ValidationError extends Error { constructor(message: string, public field: string) { super(message); this.name = 'ValidationError'; }}
class NetworkError extends Error { constructor(message: string, public statusCode: number) { super(message); this.name = 'NetworkError'; }}
// 検証機能付きの非同期関数async function validateAndFetchData(input: string): Promise<string> { console.log("データを検証中...", input); return new Promise((resolve, reject) => { setTimeout(() => { if (input === "") { reject(new ValidationError("入力が空です", "input")); } else if (input === "error") { reject(new NetworkError("サーバーエラー", 500)); } else { resolve(`処理済み: ${input}`); } }, 500); });}
// カスタムエラーの処理async function processData(): Promise<void> { const testInputs = ["正常なデータ", "", "error"];
for (const input of testInputs) { try { const result = await validateAndFetchData(input); console.log("成功:", result); } catch (error) { if (error instanceof ValidationError) { console.log(`検証エラー (${error.field}):`, error.message); } else if (error instanceof NetworkError) { console.log(`ネットワークエラー (${error.statusCode}):`, error.message); } else { console.log("不明なエラー:", error instanceof Error ? error.message : error); } } }}
processData();実行すると、以下のような結果が表示されます。
データを検証中... 正常なデータ成功: 処理済み: 正常なデータデータを検証中... 検証エラー (input): 入力が空ですデータを検証中... errorネットワークエラー (500): サーバーエラーエラー情報の型定義
エラー情報をオブジェクトとして返す方法も見てみましょう。
// エラー情報の型定義type ApiResult<T> = { success: true; data: T;} | { success: false; error: string; errorCode: number;};
// エラー情報を含む結果を返す関数async function fetchApiData(endpoint: string): Promise<ApiResult<string>> { console.log(`API呼び出し: ${endpoint}`); return new Promise((resolve) => { setTimeout(() => { if (endpoint === "/users") { resolve({ success: true, data: "ユーザーデータ" }); } else { resolve({ success: false, error: "エンドポイントが見つかりません", errorCode: 404 }); } }, 700); });}
// エラー情報を含む結果の処理async function handleApiCall(): Promise<void> { const endpoints = ["/users", "/invalid"];
for (const endpoint of endpoints) { const result = await fetchApiData(endpoint); if (result.success) { console.log("API成功:", result.data); } else { console.log(`APIエラー (${result.errorCode}):`, result.error); } }}
handleApiCall();実行すると、以下のような結果が表示されます。
API呼び出し: /usersAPI成功: ユーザーデータAPI呼び出し: /invalidAPIエラー (404): エンドポイントが見つかりませんまとめ
本章では、TypeScriptでのasync/awaitとエラーハンドリングについて学習しました。以下の内容をマスターできたことと思います。
async関数は自動的にPromiseを返し、戻り値の型はPromise<戻り値の型>となります。awaitを使用することで、Promiseから値を取り出す際にTypeScriptが自動的に適切な型を推論してくれます。
try-catch文を使用することで、非同期処理で発生するエラーを安全に処理できます。また、カスタムエラークラスを定義することで、より詳細なエラー情報を扱うことができます。
これらの知識は、実際のWebアプリケーション開発において、サーバーとの通信やファイル操作など、様々な場面で活用されます。適切なエラーハンドリングにより、ユーザーにとって使いやすく信頼性の高いアプリケーションを作ることができるようになります。
Starterプランでより詳しく学習
この先のコンテンツを読むにはStarterプラン以上が必要です。より詳細な解説、実践的なサンプルコード、演習問題にアクセスして学習を深めましょう。