JavaScriptのasync/awaitを使った非同期

jsコード JavaScript

JavaScriptの非同期処理をシンプルかつ直感的に扱うために、async/await 構文が登場しました。

非同期処理は、現代のウェブアプリケーションにおいて必須の技術ですが、従来のコールバックやPromiseを使った方法では、しばしば複雑で可読性に欠けることがありました。
async/await はこれらの問題を解決し、コードをまるで同期処理のように書けるため、開発者の負担を大幅に軽減します。

本記事では、async/await の基本から応用までを詳細に解説し、非同期処理のエラーハンドリングやベストプラクティスについても触れていきます。
さらに、実際のウェブアプリケーションにおける具体的なユースケースを通して、async/await の力を最大限に引き出す方法を学びましょう。

async/awaitの概要

async/awaitとは

async/awaitは、直前の記事でご紹介した「Promise」を、よりシンプルに扱うための構文です。

async キーワードを関数の前に付けることで、その関数が非同期関数として定義されます。
await キーワードを使用する(*1)と、Promiseが解決されるまで関数の実行を待機します。

*1:await キーワードは、Promiseを返す関数呼び出しの前に付けて使用します。これにより、Promiseが解決(成功または失敗)されるまで関数の実行が一時停止されます。

  • await キーワードは、Promiseを返す関数呼び出しの前に付けて使用します。
  • await async キーワードで定義された非同期関数の内部でのみ使用できます。

Promiseとの関係

async/awaitはPromiseの上に構築されています。これは、async/awaitが内部的にPromiseを使用して非同期処理を管理していることを意味します。
具体的には、awaitキーワードが使用されると、その非同期処理はPromiseとして扱われ、Promiseが解決されるまで関数の実行が一時停止されます。

従来のPromiseチェーンを使用する方法に比べ、async/awaitを使うことでコードの可読性が大幅に向上し、エラーハンドリングも簡潔に行うことができます。

以下に、同じ処理をPromiseとasync/awaitで作成したサンプルを示しますので、比較してみてください。

Promiseのサンプル
function fetchData() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('データ取得に成功しました');
        }, 1000);
    });
}

fetchData()
    .then(data => {
        console.log(data); // "データ取得に成功しました"
    })
    .catch(error => {
        console.error('Error:', error);
    });

👆上記の例では、関数本体がPromiseオブジェクトを返し、呼び出し側で then と catch のメソッドにより処理を繋げています。
これは、Promiseチェーンと呼ばれる仕組みです。

async/awaitのサンプル
async function fetchDataAsync() {
    try {
        const data = await new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('データ取得に成功しました');
            }, 1000);
        });
        console.log(data); // "データ取得に成功しました"
    } catch (error) {
        console.error('Error:', error);
    }
}

fetchDataAsync();

👆上記の例では、fetchDataAsync関数内で await キーワードを使用してPromiseを待機しています。
この await の動作は、then メソッドを使って結果を処理するのと同じですが、コードがより直線的で可読性が高くなっています。

Promiseとasync/await の違いを端的にまとめると次のようになります。

Promiseは、関数の呼び出し側で then を使用したPromiseチェーンにより非同期処理を行い、async/await は、関数本体で await を使用した非同期処理を行う。

async/awaitの基本的な使用方法

基本的な構文と使用例

async/awaitの基本的な構文と使用例を示します。

async/await の基本的な使用例
async function fetchData() {
    try {
        const response = await fetch('https://not.existing.com/data');
        const data = await response.json();
        console.log(data);
    } catch (error) {
        console.error('Fetch error:', error);
    }
}

fetchData();

👆上記サンプルプログラムの処理の流れは次の通りです。

  1. await fetch('https://not.existing.com/data'):
    • この行でfetch関数が呼ばれ、Promiseが返されます。
    • await キーワードにより、Promiseが解決されるまで関数の実行が一時停止されます。
    • Promiseが解決されると、responseに解決結果が格納されます。
  2. await response.json():
    • response.json()もPromiseを返すため、再び await でその解決を待ちます。
    • このPromiseが解決されるまで関数の実行が一時停止されます。
    • Promiseが解決されると、dataに解決結果が格納されます。
  3. console.log(data):
    • 上記のすべての await 処理が完了した後に、この行が実行されます。
    • console.log(data)が実行される時点では、dataに非同期処理の結果が格納されています。

何が嬉しいのか

時間のかかる処理を待たずして、先に次の処理を実行させられることです。

動作するサンプルで実例を示します。

サンプルプログラムを実際に動作させるには、次の条件があります。

  • Webサーバが必要です。
  • URLを自身のWebサーバ内に存在するものに書き換える必要があります。
sample.html
<!-- ここより上は省略 -->
<body>

<script>
async function fetchData() {
  try {
      const response = await fetch('api/data.json');
      const data = await response.json();
      console.log(data);
  } catch (error) {
      console.error('Fetch error:', error);
  }
}

fetchData();
console.log( "fetchData関数の結果よりも先に表示される" );
</script>
<!-- ここより下は省略 -->
デベロッパーツールのコンソール
fetchData関数の結果よりも先に表示される
▶ {testName: 'テスト'}

async/awaitのエラーハンドリング

非同期処理中にエラーが発生することは常に想定しておく必要があります。
async/awaitを使用することで、従来のPromiseチェーンに比べエラーハンドリングがより直感的で読みやすくなります。

エラーハンドリングについて、Promiseチェーンと async/await を比較すると以下のようになります。

  • Promiseチェーンでは、thencatchを使って非同期処理とエラーハンドリングを行います。この方法でもエラーを一元的に処理できますが、複数の非同期処理がある場合、チェーンが複雑になることがあります。
  • async/awaitでは、awaittry/catchを使って非同期処理とエラーハンドリングを行います。非同期関数内でtry/catchブロックを使用することで、エラーハンドリングを同期コードのように書けるため、読みやすく管理しやすいコードになります。

async/awaitでのエラーハンドリング例

async function fetchData() {
    try {
        const response = await fetch('https://not.existing.com/data');
        if (!response.ok) {
            throw new Error('ネットワークエラーです。');
        }
        const data = await response.json();
        console.log(data);
    } catch (error) {
        console.error('Fetch error:', error);
    }
}

fetchData();

👆上記の例では、実際には存在していないURLにアクセスしており、意図的にエラーを発生させています。
そのため、動作させると if (!response.ok) の判定が true になり、Error が投げられます。

よって、結果は以下のようになります。

デベロッパーツールのコンソール
▶ Fetch error: Error: ネットワークエラーです。

複数の非同期処理の実行

複数の非同期処理を並行して実行する場合、Promise.allasync/await を組み合わせることで効率的に処理できます。

Promise.all についての詳細は、前回の記事『JavaScriptのPromise』の Promise.allの項 をご確認ください。

Promise.all と async/await を組み合わせた複数の非同期処理サンプル

async function fetchMultipleData() {
    const urls = ['https://api.example.com/data1', 'https://api.example.com/data2'];
    try {
        const responses = await Promise.all(urls.map(url => fetch(url)));
        const data = await Promise.all(responses.map(response => response.json()));
        console.log(data);
    } catch (error) {
        console.error('Fetch error:', error);
    }
}

fetchMultipleData();

👆上記のサンプルプログラムは少し難しいので、処理の流れを説明します。

  1. 非同期関数fetchMultipleDataの定義:
    • async キーワードを使用して定義された非同期関数 fetchMultipleData 内で、複数のURLからデータをフェッチする処理を行います。
  2. URLの配列を準備:
    • const urls = ['https://api.example.com/data1', 'https://api.example.com/data2']; の部分で、フェッチするデータのURLを配列として定義します。
  3. 複数のフェッチリクエストを同時に実行:
    • const responses = await Promise.all(urls.map(url => fetch(url))); の部分で、urls 配列内の各URLに対して fetch 関数を呼び出し、Promiseの配列を生成します。
    • Promise.all を使用しているため、これらのPromiseがすべて解決(または拒否)されるまで待機し、全てのリクエストが完了した後に responses に結果を格納します。
  4. レスポンスをJSON形式に変換:
    • const data = await Promise.all(responses.map(response => response.json())); の部分で、各レスポンスオブジェクトの response.json() メソッドを呼び出し、JSON形式のデータに変換します。
      response.json() メソッドもPromiseを返すということです。)
    • 再度 Promise.all を使用して、これらのPromiseがすべて解決されるのを待機し、変換後のデータを data に格納します。
  5. データの出力:
    • console.log(data); の部分で、最終的に取得したデータをコンソールに出力します。
  6. エラーハンドリング:
    • try/catch ブロックを使用して、非同期処理中に発生する可能性のあるエラーをキャッチし、console.error でエラーメッセージを表示します。

このように、fetchリクエストとその後のresponse.jsonの処理を別々に行うことで、各フェッチリクエストが並行して実行され、その後並行してJSONに変換されます。
各ステップでエラーをキャッチしやすく、処理の途中でエラーが発生した場合に対処しやすいというメリットがあります。

async/awaitのまとめ

コードの可読性

async/awaitを使用することで、非同期処理のコードが直線的で読みやすくなります。

複雑なPromiseチェーンよりも、エラーハンドリングやデバッグが容易です。

パフォーマンスの考慮

必要以上にawaitを多用しないことで、非同期処理のパフォーマンスを最適化できます。

並行して実行できる操作は、Promise.allを使用して一度に処理しましょう。

アンチパターン

長いループ内でawaitを使用することは避け、代わりにPromise.allを使用して並行処理を行うようにします。


本記事についての質問、誤りの指摘、ご意見ご感想などありましたら、ぜひコメント頂ければ幸いです。

最後までお読みいただき、ありがとうございます。

『詳説 JavaScript』メニューに戻る

コメント

タイトルとURLをコピーしました