JavaScriptのAsync/Awaitを使ったシーケンシャルな非同期処理の解説

非同期処理は現代のJavaScriptプログラミングにおいて不可欠な技術です。特に、Webアプリケーションでは、ネットワークリクエストやファイル操作、タイマーなど多くの場面で非同期処理が必要となります。従来のコールバックやPromiseを使用した非同期処理は、コードが複雑になりがちですが、Async/Awaitを用いることで、より直感的で読みやすいコードを書くことができます。本記事では、JavaScriptのAsync/Awaitを使ったシーケンシャルな非同期処理について、その基本から応用例までを詳しく解説します。これにより、効率的で保守性の高いコードを書くためのスキルを習得できることを目指します。

目次

非同期処理の基礎知識

非同期処理とは、プログラムの他の部分の実行を待たずに、ある操作を実行する方法を指します。JavaScriptはシングルスレッドで動作するため、長時間かかる操作(ネットワークリクエストやファイル操作など)を非同期で処理しないと、ユーザーインターフェースがブロックされ、アプリケーションの応答性が低下します。

非同期処理の重要性

非同期処理は、以下の理由から重要です。

  • ユーザー体験の向上:非同期処理を使用することで、ユーザーインターフェースがスムーズに動作し続けます。
  • 効率的なリソース使用:リソースを効率的に使用し、他のタスクを並行して処理することができます。
  • 待機時間の短縮:ネットワークリクエストやI/O操作の待機時間を短縮し、全体の処理速度を向上させます。

JavaScriptにおける非同期処理の方法

JavaScriptでは、非同期処理を実現するための主な方法として、以下の3つがあります。

  • コールバック:非同期操作が完了した際に呼び出される関数を指定する方法。ネストが深くなりやすく、コールバック地獄と呼ばれるコードの複雑化を引き起こすことがあります。
  • Promise:非同期操作の完了や失敗を扱うオブジェクトで、チェーン構造により可読性を向上させます。
  • Async/Await:Promiseをさらに使いやすくする構文で、同期処理のように書くことができ、コードの可読性が大幅に向上します。

非同期処理はJavaScriptプログラムを効率的に動作させるための基本的な技術であり、Async/Awaitを理解することで、よりシンプルでメンテナンスしやすいコードを書けるようになります。

Async/Awaitの基本構文

Async/Awaitは、Promiseをより簡単に扱うための構文であり、非同期処理を同期処理のように記述することができます。これにより、非同期コードの可読性と保守性が向上します。まずは、基本的な構文と使用方法を見ていきましょう。

Asyncキーワード

関数を非同期関数として定義するためには、関数宣言の前にasyncキーワードを付けます。非同期関数は常にPromiseを返します。

async function fetchData() {
    return "データが取得されました";
}

fetchData().then(data => console.log(data)); // 出力: データが取得されました

Awaitキーワード

awaitキーワードは、Promiseの結果が返されるまで処理を一時停止します。これにより、非同期処理を同期処理のように記述できます。awaitは、async関数内でのみ使用可能です。

async function fetchData() {
    let data = await getDataFromAPI();
    console.log(data);
}

async function getDataFromAPI() {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve("APIからのデータ");
        }, 2000);
    });
}

fetchData(); // 2秒後に "APIからのデータ" を出力

実行の流れ

  1. fetchData関数が呼び出されると、内部でgetDataFromAPI関数が実行されます。
  2. awaitgetDataFromAPIからのPromiseの解決を待ちます。
  3. Promiseが解決されると、その結果がdataに格納され、次の行が実行されます。

例外処理

非同期関数内で発生したエラーは、通常のtry...catch構文を使用して処理できます。

async function fetchData() {
    try {
        let data = await getDataFromAPI();
        console.log(data);
    } catch (error) {
        console.error("エラーが発生しました:", error);
    }
}

async function getDataFromAPI() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject("データの取得に失敗しました");
        }, 2000);
    });
}

fetchData(); // 2秒後に "エラーが発生しました: データの取得に失敗しました" を出力

Async/Awaitの基本構文を理解することで、非同期処理をより直感的に記述でき、エラーハンドリングも容易になります。次のセクションでは、Async/Awaitのメリットについて詳しく見ていきます。

Async/Awaitのメリット

Async/Awaitは、Promiseをより簡単に扱うための構文であり、非同期処理を行う際に多くの利点があります。ここでは、その主要なメリットを詳しく見ていきます。

コードの可読性向上

Async/Awaitを使用することで、非同期処理がまるで同期処理のように記述できるため、コードの可読性が大幅に向上します。これは、特に複雑な非同期処理を扱う際に効果的です。

// Promiseチェーンを使用した例
function fetchData() {
    getDataFromAPI()
        .then(data => {
            console.log(data);
            return processData(data);
        })
        .then(processedData => {
            console.log(processedData);
        })
        .catch(error => {
            console.error("エラー:", error);
        });
}

// Async/Awaitを使用した例
async function fetchData() {
    try {
        let data = await getDataFromAPI();
        console.log(data);
        let processedData = await processData(data);
        console.log(processedData);
    } catch (error) {
        console.error("エラー:", error);
    }
}

エラーハンドリングの簡素化

Promiseチェーンでは、thencatchを連続して使用するため、エラーハンドリングが複雑になりがちです。Async/Awaitでは、通常のtry...catch構文を使用できるため、エラーハンドリングが簡単になります。

// Promiseチェーンを使用したエラーハンドリング
getDataFromAPI()
    .then(data => processData(data))
    .catch(error => console.error("エラー:", error));

// Async/Awaitを使用したエラーハンドリング
async function fetchData() {
    try {
        let data = await getDataFromAPI();
        let processedData = await processData(data);
    } catch (error) {
        console.error("エラー:", error);
    }
}

デバッグの容易さ

Async/Awaitを使用することで、非同期処理が同期的に記述されるため、デバッグが容易になります。デバッガを使用してステップ実行する際、非同期処理の流れを直感的に追うことができます。

ネストの回避

コールバックを多用する非同期処理は、ネストが深くなりやすく、いわゆる「コールバック地獄」に陥ることがあります。Async/Awaitを使用することで、フラットな構造のコードを書くことができ、ネストを回避できます。

// コールバックを使用した例
function fetchData(callback) {
    getDataFromAPI((error, data) => {
        if (error) return callback(error);
        processData(data, (error, processedData) => {
            if (error) return callback(error);
            callback(null, processedData);
        });
    });
}

// Async/Awaitを使用した例
async function fetchData() {
    try {
        let data = await getDataFromAPI();
        let processedData = await processData(data);
        return processedData;
    } catch (error) {
        console.error("エラー:", error);
    }
}

Async/Awaitのこれらのメリットを活用することで、非同期処理のコードがより読みやすく、保守性が高くなります。次のセクションでは、実際にAsync/Awaitを使用して非同期関数を作成する方法を解説します。

非同期関数の作成

Async/Awaitを用いて非同期関数を作成する方法を具体的に見ていきましょう。ここでは、基本的な非同期関数の作成から、複数の非同期処理をシーケンシャルに行う方法までを解説します。

基本的な非同期関数

非同期関数を作成するには、関数宣言の前にasyncキーワードを付けます。この関数内で非同期処理を行うためには、awaitキーワードを使用してPromiseの解決を待ちます。

async function fetchData() {
    try {
        let data = await getDataFromAPI();
        console.log("取得したデータ:", data);
    } catch (error) {
        console.error("エラーが発生しました:", error);
    }
}

function getDataFromAPI() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("APIからのデータ");
        }, 2000);
    });
}

fetchData();

上記の例では、fetchData関数が非同期関数として定義されており、内部でgetDataFromAPI関数が返すPromiseの解決を待っています。Promiseが解決されると、dataに結果が格納され、その値がコンソールに出力されます。

複数の非同期処理をシーケンシャルに実行

複数の非同期処理を順番に実行する場合も、awaitキーワードを使って簡単に記述できます。

async function fetchData() {
    try {
        let data1 = await getDataFromAPI1();
        console.log("API1からのデータ:", data1);

        let data2 = await getDataFromAPI2(data1);
        console.log("API2からのデータ:", data2);

        let finalData = await processFinalData(data2);
        console.log("最終データ:", finalData);
    } catch (error) {
        console.error("エラーが発生しました:", error);
    }
}

function getDataFromAPI1() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("API1からのデータ");
        }, 1000);
    });
}

function getDataFromAPI2(data) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(`API2からのデータ (API1のデータ: ${data})`);
        }, 1000);
    });
}

function processFinalData(data) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(`最終データ (API2のデータ: ${data})`);
        }, 1000);
    });
}

fetchData();

この例では、fetchData関数が3つの非同期処理を順番に実行しています。各非同期処理の結果が次の処理に渡されるため、シーケンシャルな非同期処理が実現されています。

エラーハンドリングの追加

非同期処理では、エラーが発生する可能性があるため、適切なエラーハンドリングを行うことが重要です。try...catch構文を使用してエラーをキャッチし、適切に対処します。

async function fetchData() {
    try {
        let data = await getDataFromAPI();
        console.log("取得したデータ:", data);
    } catch (error) {
        console.error("エラーが発生しました:", error);
    }
}

function getDataFromAPI() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject("データの取得に失敗しました");
        }, 2000);
    });
}

fetchData();

この例では、getDataFromAPI関数がエラーを返すように設定されており、fetchData関数内のtry...catch構文でエラーが適切にキャッチされ、コンソールにエラーメッセージが出力されます。

これで、Async/Awaitを使用して非同期関数を作成する基本的な方法が理解できたと思います。次のセクションでは、Async/Awaitを用いたシーケンシャルな非同期処理の実装方法についてさらに詳しく見ていきます。

シーケンシャルな処理の実装

Async/Awaitを使用することで、複数の非同期処理を順番に実行するシーケンシャルな処理を簡単に実装できます。ここでは、具体的な例を通じて、その方法を詳しく解説します。

シーケンシャル処理の基本

シーケンシャルな非同期処理は、各処理が完了するのを待ってから次の処理を開始することを意味します。これにより、処理の順序が保証され、データの依存関係を正しく管理できます。

async function sequentialProcessing() {
    try {
        console.log("処理1を開始します");
        let result1 = await asyncTask1();
        console.log("処理1の結果:", result1);

        console.log("処理2を開始します");
        let result2 = await asyncTask2(result1);
        console.log("処理2の結果:", result2);

        console.log("処理3を開始します");
        let result3 = await asyncTask3(result2);
        console.log("処理3の結果:", result3);

        console.log("全ての処理が完了しました");
    } catch (error) {
        console.error("エラーが発生しました:", error);
    }
}

function asyncTask1() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("タスク1の結果");
        }, 1000);
    });
}

function asyncTask2(data) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(`タスク2の結果 (前の結果: ${data})`);
        }, 1000);
    });
}

function asyncTask3(data) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(`タスク3の結果 (前の結果: ${data})`);
        }, 1000);
    });
}

sequentialProcessing();

実行結果の順序保証

上記のコードでは、asyncTask1asyncTask2asyncTask3の各タスクが順番に実行され、それぞれの結果が次のタスクに渡されています。このようにすることで、処理の順序が保証され、データの依存関係が正しく管理されます。

データの依存関係を管理する

シーケンシャルな非同期処理を使用することで、各ステップの結果を次のステップに渡すことができます。これにより、データの依存関係を適切に管理し、正しい結果を得ることができます。

async function processData() {
    try {
        let initialData = await fetchInitialData();
        let processedData = await processDataStep1(initialData);
        let finalData = await processDataStep2(processedData);
        console.log("最終データ:", finalData);
    } catch (error) {
        console.error("データ処理中にエラーが発生しました:", error);
    }
}

function fetchInitialData() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("初期データ");
        }, 1000);
    });
}

function processDataStep1(data) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(`ステップ1処理済みデータ (元データ: ${data})`);
        }, 1000);
    });
}

function processDataStep2(data) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(`ステップ2処理済みデータ (元データ: ${data})`);
        }, 1000);
    });
}

processData();

この例では、初期データを取得し、それをステップ1で処理し、さらにステップ2で最終処理を行っています。各ステップの結果が次のステップに渡されるため、データの流れが明確で管理しやすくなります。

非同期処理の順序を保つためのポイント

  • 各処理の結果を次の処理に渡す: awaitを使用して各非同期処理の結果を待ち、それを次の処理に渡します。
  • エラーハンドリングを忘れずに: try...catch構文を使用して、非同期処理中のエラーを適切に処理します。
  • 関数の分割と再利用: 各非同期処理を関数として分割し、必要に応じて再利用できるようにします。

Async/Awaitを使ったシーケンシャルな非同期処理を理解することで、複雑な非同期フローをシンプルに実装できるようになります。次のセクションでは、Async/Awaitを使ったエラーハンドリングについて詳しく解説します。

エラーハンドリング

Async/Awaitを使用した非同期処理では、エラーハンドリングが重要な要素となります。エラーが発生した場合でも、プログラムが適切に対処できるようにするための方法を見ていきましょう。

基本的なエラーハンドリング

Async/Awaitを使ったエラーハンドリングは、通常の同期処理と同じようにtry...catch構文を使用します。これにより、非同期処理中に発生したエラーを簡単にキャッチし、処理することができます。

async function fetchData() {
    try {
        let data = await getDataFromAPI();
        console.log("取得したデータ:", data);
    } catch (error) {
        console.error("エラーが発生しました:", error);
    }
}

function getDataFromAPI() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject("データの取得に失敗しました");
        }, 2000);
    });
}

fetchData();

この例では、getDataFromAPI関数がエラーを返すように設定されており、fetchData関数内のtry...catch構文でエラーが適切にキャッチされ、コンソールにエラーメッセージが出力されます。

非同期関数内での複数のエラーハンドリング

非同期関数内で複数の非同期操作を行う場合、それぞれの操作に対してエラーハンドリングを行うことが重要です。これにより、どのステップでエラーが発生したのかを特定しやすくなります。

async function sequentialProcessing() {
    try {
        let result1 = await asyncTask1();
        console.log("タスク1の結果:", result1);
    } catch (error) {
        console.error("タスク1でエラーが発生しました:", error);
        return;
    }

    try {
        let result2 = await asyncTask2();
        console.log("タスク2の結果:", result2);
    } catch (error) {
        console.error("タスク2でエラーが発生しました:", error);
        return;
    }

    try {
        let result3 = await asyncTask3();
        console.log("タスク3の結果:", result3);
    } catch (error) {
        console.error("タスク3でエラーが発生しました:", error);
    }
}

function asyncTask1() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("タスク1の結果");
        }, 1000);
    });
}

function asyncTask2() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject("タスク2のエラー");
        }, 1000);
    });
}

function asyncTask3() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("タスク3の結果");
        }, 1000);
    });
}

sequentialProcessing();

この例では、各非同期タスクに対して個別にtry...catchを使用してエラーハンドリングを行っています。これにより、特定のタスクでエラーが発生した場合でも、他のタスクへの影響を最小限に抑えることができます。

全体のエラーハンドリング

場合によっては、すべての非同期操作を一括してエラーハンドリングすることも可能です。その場合は、最上位の非同期関数でtry...catchを使用します。

async function fetchData() {
    try {
        let result1 = await asyncTask1();
        console.log("タスク1の結果:", result1);

        let result2 = await asyncTask2();
        console.log("タスク2の結果:", result2);

        let result3 = await asyncTask3();
        console.log("タスク3の結果:", result3);
    } catch (error) {
        console.error("エラーが発生しました:", error);
    }
}

function asyncTask1() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("タスク1の結果");
        }, 1000);
    });
}

function asyncTask2() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject("タスク2のエラー");
        }, 1000);
    });
}

function asyncTask3() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("タスク3の結果");
        }, 1000);
    });
}

fetchData();

この例では、最上位のfetchData関数でtry...catchを使用して、すべての非同期操作に対するエラーを一括して処理しています。これにより、コードが簡潔になり、一箇所でエラーを管理することができます。

エラーハンドリングのベストプラクティス

  • 適切なレベルでエラーハンドリングを行う: 各非同期操作ごとにエラーハンドリングを行うか、全体でまとめて行うかを状況に応じて選択します。
  • エラーメッセージを明確にする: エラーメッセージは具体的でわかりやすいものにし、デバッグや問題解決を容易にします。
  • 必要に応じてリトライロジックを実装する: 一部のエラーについては再試行するロジックを組み込むことで、信頼性を向上させることができます。

これで、Async/Awaitを使ったエラーハンドリングの基本が理解できたと思います。次のセクションでは、Async/Awaitを利用した具体的な応用例として、APIの連続呼び出しについて見ていきます。

応用例:APIの連続呼び出し

Async/Awaitを使った非同期処理の強力な応用例として、複数のAPIを順番に呼び出すシナリオを紹介します。ここでは、最初のAPIから取得したデータを使って次のAPIを呼び出す連続的な処理を実装します。

シナリオの説明

例えば、ユーザー情報を取得するために最初のAPIを呼び出し、そのユーザーIDを使ってユーザーの詳細情報を取得する次のAPIを呼び出すといったケースです。このような連続呼び出しをシーケンシャルに行うためには、Async/Awaitが非常に有効です。

実装例

以下の例では、まずユーザーリストを取得し、その中の最初のユーザーの詳細情報を取得します。

async function fetchUserData() {
    try {
        // 最初のAPI呼び出し:ユーザーリストを取得
        let users = await getUsers();
        console.log("ユーザーリスト:", users);

        // 取得したユーザーIDを使用して、次のAPI呼び出し:ユーザーの詳細情報を取得
        let userId = users[0].id;
        let userDetails = await getUserDetails(userId);
        console.log("ユーザー詳細情報:", userDetails);
    } catch (error) {
        console.error("エラーが発生しました:", error);
    }
}

// ユーザーリストを取得する非同期関数
function getUsers() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve([
                { id: 1, name: "ユーザー1" },
                { id: 2, name: "ユーザー2" }
            ]);
        }, 1000);
    });
}

// ユーザーの詳細情報を取得する非同期関数
function getUserDetails(userId) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve({ id: userId, name: "ユーザー詳細", age: 25 });
        }, 1000);
    });
}

fetchUserData();

コードの説明

  1. getUsers関数: ユーザーリストを取得する非同期関数です。ここでは、1秒後にユーザーリストを返すPromiseを返します。
  2. getUserDetails関数: 特定のユーザーの詳細情報を取得する非同期関数です。ユーザーIDを受け取り、1秒後にそのユーザーの詳細情報を返すPromiseを返します。
  3. fetchUserData関数: getUsers関数でユーザーリストを取得し、その結果を使用してgetUserDetails関数でユーザーの詳細情報を取得する非同期関数です。各API呼び出しの結果をawaitキーワードで待ち、順番に処理を進めています。

エラーハンドリング

例では、try...catch構文を使ってAPI呼び出し中に発生する可能性のあるエラーをキャッチし、適切に処理しています。これにより、各ステップでエラーが発生した場合でも、エラーメッセージが表示され、問題の原因を特定しやすくなります。

API連続呼び出しの利点

  • コードの可読性: Async/Awaitを使用することで、コードが同期処理のように直線的に記述され、可読性が向上します。
  • エラーハンドリングの統一: try...catchを使うことで、エラーハンドリングが統一され、管理が容易になります。
  • データの依存関係の管理: 各API呼び出しの結果を次のAPI呼び出しに利用する場合、データの依存関係を明確に管理できます。

このように、Async/Awaitを使ったAPIの連続呼び出しは、実際のアプリケーション開発において非常に有用です。次のセクションでは、ファイル操作におけるシーケンシャルな非同期処理の具体例を紹介します。

応用例:ファイル操作

Async/Awaitを使った非同期処理は、ファイル操作にも非常に有効です。ここでは、ファイルの読み込み、処理、そして保存といった一連の操作をシーケンシャルに実行する例を見ていきます。

シナリオの説明

例えば、ログファイルを読み込み、その内容を解析し、結果を新しいファイルに保存するといったケースです。このようなシーケンシャルなファイル操作を非同期で行うことで、操作中のブロッキングを回避し、アプリケーションの応答性を保つことができます。

実装例

以下の例では、Node.jsのfsモジュールを使用して、ファイルの読み込み、処理、保存を行います。

const fs = require('fs').promises;

async function processLogFile() {
    try {
        // ファイルを読み込む
        let data = await fs.readFile('log.txt', 'utf8');
        console.log("読み込んだデータ:", data);

        // データを処理する(例:大文字に変換)
        let processedData = data.toUpperCase();
        console.log("処理済みデータ:", processedData);

        // 処理済みデータを新しいファイルに保存する
        await fs.writeFile('processed_log.txt', processedData);
        console.log("処理済みデータが保存されました");
    } catch (error) {
        console.error("エラーが発生しました:", error);
    }
}

processLogFile();

コードの説明

  1. fs.readFile関数: fsモジュールのreadFile関数を使用して、非同期にファイルを読み込みます。awaitキーワードを使用して、ファイルの読み込みが完了するまで待ちます。
  2. データの処理: 読み込んだデータを大文字に変換する処理を行います。この部分は任意のデータ処理を行うことができます。
  3. fs.writeFile関数: fsモジュールのwriteFile関数を使用して、非同期にファイルにデータを書き込みます。awaitキーワードを使用して、ファイルの書き込みが完了するまで待ちます。

エラーハンドリング

例では、try...catch構文を使用して、ファイル操作中に発生する可能性のあるエラーをキャッチし、適切に処理しています。これにより、ファイルの読み込みや書き込み中にエラーが発生した場合でも、エラーメッセージが表示され、問題の原因を特定しやすくなります。

複数ファイルの連続処理

複数のファイルを順番に処理する場合も、Async/Awaitを使うことで簡単に実装できます。以下の例では、複数のファイルを順番に読み込み、処理し、保存しています。

const fs = require('fs').promises;

async function processMultipleFiles() {
    try {
        // ファイル1を読み込む
        let data1 = await fs.readFile('file1.txt', 'utf8');
        console.log("ファイル1のデータ:", data1);

        // ファイル2を読み込む
        let data2 = await fs.readFile('file2.txt', 'utf8');
        console.log("ファイル2のデータ:", data2);

        // ファイル1とファイル2のデータを結合して新しいファイルに保存する
        let combinedData = data1 + "\n" + data2;
        await fs.writeFile('combined.txt', combinedData);
        console.log("結合データが保存されました");
    } catch (error) {
        console.error("エラーが発生しました:", error);
    }
}

processMultipleFiles();

この例では、file1.txtfile2.txtの内容を順番に読み込み、それらを結合してcombined.txtという新しいファイルに保存しています。

ファイル操作の利点

  • 非同期処理による応答性の向上: ファイル操作を非同期で行うことで、操作中に他の処理がブロックされず、アプリケーションの応答性が向上します。
  • シーケンシャルな処理の実現: Async/Awaitを使用することで、ファイル操作を順番に実行し、データの依存関係を適切に管理できます。
  • エラーハンドリングの一元化: try...catchを使用して、ファイル操作中のエラーを一元的に管理できます。

Async/Awaitを使用したファイル操作により、複雑な非同期ファイル処理もシンプルに実装できます。次のセクションでは、読者が理解を深めるための演習問題を提供します。

演習問題

Async/Awaitを使った非同期処理についての理解を深めるために、いくつかの演習問題を用意しました。各問題に取り組むことで、実践的なスキルを身に付けることができます。

演習問題1:APIからのデータ取得と表示

以下のAPIエンドポイントからデータを取得し、コンソールに表示する非同期関数を作成してください。
APIエンドポイント: https://jsonplaceholder.typicode.com/posts/1

async function fetchPost() {
    // ここにコードを書いてください
}

fetchPost();

演習問題2:複数のAPI呼び出し

以下の2つのAPIエンドポイントからデータを順番に取得し、コンソールに表示する非同期関数を作成してください。

  1. https://jsonplaceholder.typicode.com/posts/1
  2. https://jsonplaceholder.typicode.com/users/1
async function fetchPostAndUser() {
    // ここにコードを書いてください
}

fetchPostAndUser();

演習問題3:ファイルの読み込みと書き込み

Node.jsのfsモジュールを使用して、input.txtというファイルを読み込み、その内容を大文字に変換してoutput.txtという新しいファイルに書き込む非同期関数を作成してください。

const fs = require('fs').promises;

async function processFile() {
    // ここにコードを書いてください
}

processFile();

演習問題4:エラーハンドリングの追加

演習問題1のfetchPost関数にエラーハンドリングを追加し、API呼び出しが失敗した場合に適切なエラーメッセージを表示するようにしてください。

async function fetchPost() {
    try {
        // ここにコードを書いてください
    } catch (error) {
        console.error("エラーが発生しました:", error);
    }
}

fetchPost();

演習問題5:複数ファイルの連続処理

Node.jsのfsモジュールを使用して、file1.txtfile2.txtを順番に読み込み、それらの内容を結合してcombined.txtという新しいファイルに書き込む非同期関数を作成してください。

const fs = require('fs').promises;

async function combineFiles() {
    // ここにコードを書いてください
}

combineFiles();

演習問題6:APIのデータを加工して保存

以下のAPIエンドポイントからユーザー情報を取得し、そのユーザー名を大文字に変換して、user.txtというファイルに保存する非同期関数を作成してください。
APIエンドポイント: https://jsonplaceholder.typicode.com/users/1

const fs = require('fs').promises;

async function saveUserName() {
    // ここにコードを書いてください
}

saveUserName();

演習問題のまとめ

これらの演習問題に取り組むことで、Async/Awaitを使用した非同期処理の基本と応用を実践的に学ぶことができます。実際に手を動かしてコードを書き、動作を確認することで、理解を深めてください。各問題の回答例も用意しておくと、学習効果がさらに高まります。

次のセクションでは、記事全体のまとめを行います。

まとめ

本記事では、JavaScriptのAsync/Awaitを使用したシーケンシャルな非同期処理について詳しく解説しました。非同期処理の基礎知識から始まり、Async/Awaitの基本構文、メリット、非同期関数の作成方法、シーケンシャルな処理の実装方法、エラーハンドリングの方法を段階的に説明しました。また、APIの連続呼び出しやファイル操作といった実際の応用例も紹介しました。

Async/Awaitを使用することで、複雑な非同期処理も簡潔で可読性の高いコードで実装できるようになります。また、エラーハンドリングやデータの依存関係の管理も容易になるため、より信頼性の高いアプリケーションを開発することができます。

さらに、演習問題を通じて実践的なスキルを身に付ける機会を提供しました。これらの演習に取り組むことで、Async/Awaitの使用方法を実践的に理解し、応用する力を養うことができます。

非同期処理は現代のJavaScript開発において不可欠なスキルです。この記事を通じてAsync/Awaitの利便性と強力さを実感し、日々の開発に役立ててください。

これで、JavaScriptのAsync/Awaitを使ったシーケンシャルな非同期処理についての解説を終わります。引き続き学習を進め、より高度な非同期処理の技術を習得していきましょう。

コメント

コメントする

目次