カテゴリ: TypeScript 更新日: 2026/03/07

TypeScriptでasync/awaitを共通化!再利用可能な非同期ユーティリティ設計ガイド

TypeScriptで再利用可能なasyncユーティリティを設計する方法
TypeScriptで再利用可能なasyncユーティリティを設計する方法

先生と生徒の会話形式で理解しよう

生徒

「TypeScriptで非同期処理を書いていると、同じようなエラーハンドリングや待ち合わせ処理が何度も出てきて、コードがごちゃごちゃになってしまいます。」

先生

「それは便利なユーティリティ関数を作るタイミングですね。非同期処理、つまりPromiseやasync/awaitを共通化することで、スッキリとした再利用可能なコードが書けるようになりますよ。」

生徒

「具体的には、どのようにして便利な道具箱を作ればいいのでしょうか?」

先生

「初心者の方でも分かりやすいように、順番に設計方法を解説していきますね!」

1. 非同期処理とPromiseの基本を知ろう

1. 非同期処理とPromiseの基本を知ろう
1. 非同期処理とPromiseの基本を知ろう

まずは非同期処理について説明します。普通のプログラムは、上から下へと順番に実行されます。しかし、インターネットからデータを取得する場合など、返事が来るまで時間がかかることがあります。この「待ち時間」の間、他の作業を止めてしまうとパソコンが固まってしまいます。そこで登場するのが、非同期処理です。

非同期処理とは、時間のかかる作業を裏側で進めておき、終わったら結果を教えてもらう仕組みのことです。TypeScriptでは、この約束をPromise(プロミス)という名前で呼びます。文字通り「未来に結果を返すという約束」という意味です。

プログラミングに慣れていない方にとって、この「後で実行される」という感覚は少し難しいかもしれませんが、料理に例えると分かりやすいです。パスタを茹でている間にソースを作りますよね。パスタが茹であがるのをじっと待ってからソースを作り始めるのではなく、並行して作業を進めるのが非同期処理のイメージです。

2. asyncとawaitでコードを読みやすくする

2. asyncとawaitでコードを読みやすくする
2. asyncとawaitでコードを読みやすくする

Promiseをより簡単に扱うための魔法の言葉がasync(エイシンク)await(アウェイト)です。これらを使うことで、難しい非同期処理があたかも普通の順番通りの処理のように書けるようになります。

関数にasyncをつけると、その関数は「非同期な関数」になります。そして、その中でawaitを使うと、時間のかかる処理が終わるまでその場で見守ってくれるようになります。ただし、待っている間も他のプログラムは動いているので、全体の動作が止まることはありません。

例えば、外部のサーバーからユーザーの情報を取ってくるプログラムを考えてみましょう。普通に書くと複雑になりますが、これらを使うと非常にシンプルになります。


// データを取得するシミュレーション関数
async function fetchUserData() {
    console.log("データの取得を開始します...");
    
    // 3秒待機する処理
    await new Promise(resolve => setTimeout(resolve, 3000));
    
    console.log("データの取得が完了しました!");
    return { id: 1, name: "太郎" };
}

// 実行
fetchUserData();

実行結果は以下のようになります。


データの取得を開始します...
(3秒後)
データの取得が完了しました!

3. 再利用可能なユーティリティを設計する理由

3. 再利用可能なユーティリティを設計する理由
3. 再利用可能なユーティリティを設計する理由

なぜ「ユーティリティ(共通の道具)」を作る必要があるのでしょうか。それは、開発が進むにつれて「データの取得に失敗したときの処理」や「読み込み中に出すメッセージの管理」など、同じようなプログラムを何度も書くことになるからです。

同じことを何度も書くと、どこか一箇所を修正したいときに、全ての場所を直さなければならず、ミスが発生しやすくなります。そこで、共通のパターンを一つの関数としてまとめておき、どこからでも呼び出せるように設計するのが、プログラミングにおける再利用性の向上です。

特に非同期処理では、エラーが起きたときの対策(例外処理)が重要です。これを毎回書くのは大変なので、自動的にエラーをキャッチしてくれる便利な道具を作ると、開発がぐっと楽になります。

4. 非同期処理を包み込むラッパー関数を作る

4. 非同期処理を包み込むラッパー関数を作る
4. 非同期処理を包み込むラッパー関数を作る

最初におすすめするユーティリティは、エラーハンドリングを簡単にするラッパー関数です。通常、非同期処理のエラーを防ぐにはtry-catchという構文を使いますが、これが何度も出てくるとコードが読みづらくなります。

そこで、関数の実行結果を「成功したときのデータ」と「失敗したときのエラー」のセットで返してくれる道具を作ってみましょう。これにより、呼び出す側ではスッキリとした条件分岐だけで済みます。


// 共通のユーティリティ関数
async function safeAsync<T>(promise: Promise<T>) {
    try {
        const data = await promise;
        return [data, null] as const;
    } catch (error) {
        return [null, error] as const;
    }
}

// 使い方
async function main() {
    const fetchTask = new Promise<string>((resolve, reject) => {
        // 成功ならresolve、失敗ならrejectを呼ぶ
        resolve("成功データ");
    });

    const [result, err] = await safeAsync(fetchTask);

    if (err) {
        console.log("エラーが発生しました:", err);
        return;
    }
    console.log("受け取った内容:", result);
}

main();

実行結果は以下のようになります。


受け取った内容: 成功データ

このように、成功と失敗を一つの配列として受け取る形式にすることで、コードの見た目が非常に整理されます。これを設計と呼び、開発の効率を大きく左右します。

5. タイムアウト機能を共通化して制御する

5. タイムアウト機能を共通化して制御する
5. タイムアウト機能を共通化して制御する

インターネットの通信状況によっては、返事がいつまでも返ってこないことがあります。これを放置すると、利用者はずっと待ち続けることになってしまいます。そこで、一定時間が過ぎたら強制的に終了させる「タイムアウト機能」をユーティリティとして持っておくと非常に便利です。

これも一つの関数として用意しておくことで、どの通信処理にも簡単に制限時間を設けることができるようになります。TypeScriptの型定義をしっかり行うことで、どのようなデータが返ってくる関数にも対応させることが可能です。


// 指定した時間内に終わらなければエラーにするユーティリティ
async function withTimeout<T>(promise: Promise<T>, timeoutMs: number) {
    const timeoutPromise = new Promise<never>((_, reject) => {
        setTimeout(() => reject(new Error("時間切れです")), timeoutMs);
    });

    // どちらか早い方を採用する仕組み
    return Promise.race([promise, timeoutPromise]);
}

// 使い方
async function runTask() {
    const slowTask = new Promise(resolve => setTimeout(() => resolve("完了"), 5000));
    
    try {
        // 2秒でタイムアウトさせる設定
        const result = await withTimeout(slowTask, 2000);
        console.log(result);
    } catch (e) {
        console.log("メッセージ:", e.message);
    }
}

runTask();

実行結果は以下のようになります。


メッセージ: 時間切れです

6. リトライ処理(再試行)の自動化

6. リトライ処理(再試行)の自動化
6. リトライ処理(再試行)の自動化

通信エラーは一時的なものであることが多いです。一回失敗しただけで諦めるのではなく、数回やり直すことで成功する可能性があります。この「やり直し」の処理を毎回書くのは面倒ですよね。そこで、自動で数回リトライしてくれるユーティリティを設計しましょう。

このユーティリティは、指定した回数だけ処理を繰り返し、すべて失敗した場合に初めてエラーを出すように作ります。これによって、システムの安定性が格段に向上します。


// 失敗しても指定回数やり直すユーティリティ
async function retry<T>(task: () => Promise<T>, count: number): Promise<T> {
    let lastError: any;
    
    for (let i = 0; i < count; i++) {
        try {
            console.log(`${i + 1}回目の挑戦中...`);
            return await task();
        } catch (error) {
            lastError = error;
        }
    }
    
    throw new Error("すべての試行に失敗しました: " + lastError);
}

// 実行例(わざと失敗させる)
const dangerousTask = async () => {
    throw "ネットワークエラー";
};

retry(dangerousTask, 3).catch(err => console.log(err.message));

実行結果は以下のようになります。


1回目の挑戦中...
2回目の挑戦中...
3回目の挑戦中...
すべての試行に失敗しました: ネットワークエラー

7. TypeScriptで型安全な設計を意識する

7. TypeScriptで型安全な設計を意識する
7. TypeScriptで型安全な設計を意識する

ここまでの解説で、いくつかのユーティリティを見てきました。TypeScriptの最大の特徴は型(かた)があることです。型とは、そのデータが「数字なのか」「文字なのか」「特別なリストなのか」を明確に決めるルールのことです。

ユーティリティを作るときに、どんなデータでも扱えるように「ジェネリクス(Generics)」という機能を使います。コードの中で<T>のように書かれている部分がそれです。これは「後から決まる型」という意味で、これを使うことで一つの道具をどんな種類のデータにも使い回せるようになります。

プログラミング未経験の方には少し難しく感じるかもしれませんが、「中身が何であっても壊れない、頑丈な入れ物を作っている」と考えてみてください。この頑丈な設計こそが、大規模な開発でバグを防ぐ鍵となります。

8. 複数の非同期処理をまとめて管理する

8. 複数の非同期処理をまとめて管理する
8. 複数の非同期処理をまとめて管理する

最後に、複数の作業を同時に行う場合の設計について考えます。例えば、三つの異なる場所からデータを集めたいとき、一つずつ順番に待っていると時間がもったいないです。これらを一気に開始して、全員が戻ってくるのを待つ方法があります。

TypeScriptにはPromise.allという標準機能がありますが、これもユーティリティとしてラップすることで、より使いやすくカスタマイズできます。例えば、どれか一つが失敗しても他の結果は捨てないようにするといった、柔軟な設計が可能です。

このように、非同期処理をただ使うだけでなく、自分のプロジェクトに合わせて「使いやすい形に整える」ことが、プロフェッショナルなプログラミングへの第一歩です。最初は難しく感じるかもしれませんが、少しずつ自分で便利な関数を増やしていく楽しさを味わってみてください。

9. 学んだ知識を実践に活かすために

9. 学んだ知識を実践に活かすために
9. 学んだ知識を実践に活かすために

非同期処理のユーティリティ設計は、最初は真似をすることから始めましょう。今回紹介したsafeAsyncretryなどは、実際の現場でもよく使われる形です。これらを自分のプログラムに取り入れるだけで、コードが劇的に綺麗になります。

パソコンの操作に慣れていない方でも、まずはコードをコピーして動かしてみることから挑戦してみてください。エラーが出たら、何が原因かメッセージを読んで考える。その繰り返しの先に、自由自在にプログラムを操る力が身につきます。

TypeScriptは非常に人気のある言語で、一度覚えるとウェブサイト作成からアプリ開発まで幅広く活躍できます。非同期処理という壁を乗り越えて、素晴らしいエンジニアへの道を歩んでいきましょう。

カテゴリの一覧へ
新着記事
New1
TypeScript
TypeScriptで非同期処理の失敗をリトライ(再試行)する方法を初心者向けに徹底解説!
New2
JavaScript
JavaScriptのデータ型の自動変換に注意しよう!型変換の罠まとめ
New3
JavaScript
JavaScriptのNumber関数で文字列を数値に変換する方法をやさしく解説!初心者でもわかる型変換の基本
New4
JavaScript
JavaScriptでセレクトボックスの値を取得設定する方法|初心者向けフォーム操作完全解説
人気記事
No.1
Java&Spring記事人気No1
JavaScript
JavaScriptプログラムの実行方法まとめ!ブラウザ・Node.js・コンソールの使い方
No.2
Java&Spring記事人気No2
JavaScript
JavaScriptのインストール方法まとめ!Windows・Mac・Linux別にステップ解説
No.3
Java&Spring記事人気No3
JavaScript
JavaScriptで文字列をforループで1文字ずつ処理する方法!初心者向け解説
No.4
Java&Spring記事人気No4
TypeScript
TypeScriptでasync/awaitを共通化!再利用可能な非同期ユーティリティ設計ガイド
No.5
Java&Spring記事人気No5
JavaScript
JavaScriptでフォームの値を設定する方法を徹底解説|初心者向けフォーム操作入門
No.6
Java&Spring記事人気No6
JavaScript
JavaScriptのonclick・onchangeなどの基本イベントを理解しよう
No.7
Java&Spring記事人気No7
TypeScript
TypeScriptでコメントを書く正しい書き方と使い分け【初心者向けにやさしく解説】
No.8
Java&Spring記事人気No8
JavaScript
JavaScriptで要素の高さ・幅を取得する方法を完全解説!offsetHeightなどDOM操作入門