🔖

【イラスト付き】JavaScript非同期処理の実行【丁寧に解説】

2024/09/04に公開

はじめに

皆さんこんにちは。

今回はJavaScriptでの非同期処理の呼び出し方をご紹介します。

非同期処理はかつてはコールバック地獄という書きづらい側面がありましたが、時代の変遷とともにどんどん書きやすくなってきています。

こんな人にオススメ

  • これから非同期処理を本格的に扱いたい人
  • 実用的な書き方を知りたい人

初めて学習する方にも分かるように、丁寧に解説していきます。
プログラミングに慣れてきた方も、是非一度目を通していただけると嬉しいです。

なお、前提知識として非同期処理の仕組みとPromiseオブジェクトの理解が必要です。

😋 非同期処理はJavaScriptで頻繁に利用します♪

async/await

まずポイントをチェック

  • 非同期処理を同期的に記述できる
  • 非同期処理の結果を戻り値として受け取ることができる
  • Promiseチェーンのシンタックスシュガー

async/awaitは非同期処理を同期的に記述することができる構文です。非同期処理の結果を戻り値として受け取ることができるようになります。

async/awaitの書き方

  • 関数宣言の先頭にasyncキーワードをつける
  • async関数内で非同期処理を呼び出す際は先頭にawaitキーワードをつける
  • awaitをつけた非同期処理の結果は戻り値として受け取る
  • エラー処理はtry-catchを利用する

awaitキーワードをつけた非同期処理はPromiseが解決されるまで待機されます。Promiseが解決されると結果を戻り値として受け取り、次の行に処理が移ります。

asyncキーワードをつけた関数を非同期関数といいます。非同期関数自体の実行は非同期で行われます。関数内の記述が同期的なので誤解されやすいですが、非同期関数がシングルスレッドを塞ぐことはありません。

async function fn () {
    try{
        const 変数名 = await 非同期処理();
    } catch (エラーオブジェクト) {
        エラー処理
    }
}
サンプルコード(fetch関数でGETリクエストを送る)
const fn = async () => {
    try {
        const response = await fetch('URL');
        const data = await response.json();
        console.log(data);
    } catch (error) {
        console.error(error);
    }
};

😋 非同期処理はasync/awaitで記述することが多いです♪

Promise.all

まずポイントをチェック

  • 複数の非同期処理を同時に実行できる
  • 全ての非同期処理が終了するのを待ち合わせる
  • 非同期処理の結果は配列になる
  • Promise.allにもasync/awaitを利用可能

Promise.allメソッドは複数の非同期処理を同時に実行し、全ての非同期処理が完了するまで待ち合わせます。

🍕 例えば、ページの描画に必要なデータを複数回のGETリクエストで取得する場合、一つ一つの結果を取得してから次のGETリクエストを行うと効率が悪くなります。Promise.allで同時にGETリクエストを送信し結果を待ち合わせることで、効率よくデータを取得できます。

Promise.allの書き方

  • Promise.allメソッドの引数にPromiseオブジェクトの配列を指定
    • これが複数の非同期処理の指定です
  • 全ての非同期処理が成功した場合、非同期処理の結果を含む配列を返す
  • いずれかの非同期処理が失敗した場合はエラーが発生
  • async/await を利用する場合は、Promise.allの先頭にawaitを付ける

Promise.allメソッドの戻り値はPromiseオブジェクトであるため、async/awaitを利用することができます。

const fn = async () => {
    const results = await Promise.all([Promiseの配列]);
};
サンプルコード(複数のGETリクエストを送信)
const fn = async () => {
    try {
        const responses = await Promise.all([
            fetch('URL'),
            fetch('URL')
        ]);
        const datas = await Promise.all(responses.map(response => response.json()));
        datas.forEach((data) => console.log(data));
    } catch (error) {
        console.log(error);
    }
};

😋 複数の非同期処理をまとめて行えます♪

最上位のawait(Top-level await)

まずポイントをチェック

  • モジュールの場合、asyncなしで単独でawaitを利用可能
  • モジュール全体が1つの非同期関数のような扱い

Top-level awaitはES2022で追加された言語仕様です。従来の言語仕様ではawaitキーワードは非同期関数内でのみ利用可能でした。Top-level awaitの導入によりモジュール内であれば単独でawaitキーワードが使えるようになりました。

🍕 従来の場合、非同期処理にawaitキーワードを使いたい時は非同期関数を宣言していました。Top-level awaitの導入によりちょっと非同期処理をする際にいちいち非同期関数を宣言する煩わしさから解放されます。

Top-level awaitの書き方

  • モジュール分割されていること
  • 通常のawaitキーワードの使い方と同様
try {
    const response = await 非同期処理;
} catch (error) {
    console.error(error);
}
サンプルコード(fetch関数でGETリクエストを送る)
try {
    const response = await fetch('URL');
    const data = await response.json();
    console.log(data);
} catch (error) {
    console.error(error);
}

😋 いちいちasync関数を宣言する煩わしさから解放されます♪

【おまけ】Promiseチェーン

まずポイントをチェック

  • 非同期処理を連鎖的に繋げて記述できる
  • async/await登場以前はこの方法を使っていた

Promiseチェーンは非同期処理を連鎖的に記述する方法です。Promise以前の非同期処理はコールバック地獄(関数の中でコーバック関数を呼びネストが深くなりすぎる)になっていました。Promiseオブジェクトの登場とPromiseチェーンの活用によりネストを深くせずに、非同期処理を記述することができるようになりました。

Promiseチェーンの書き方

  • 非同期処理の実行を記述
  • 処理成功時の処理をthenメソッドのコールバック関数として記述
    • 非同期処理の結果はthenのコールバック関数の引数で受け取ります
    • thenのコールバック関数の戻り値は次のthenのコールバック関数の引数に渡ります
  • 処理失敗時の処理をcatchメソッドのコールバック関数として記述

Promiseチェーンはthenやcatchの戻り値がPromiseオブジェクトであることを利用したメソッドチェーンです。

非同期処理()
    .then((結果) => 成功時処理)
    .then((直前の処理の戻り値) => 引数を使ってやりたい処理)
    .catch((error) => エラー処理);
サンプルコード(fetch関数でGETリクエストを送る)
fetch('URL')
    .then((response) => response.json())
    .then((data) => console.log(data))
    .catch((error) => console.error(error));

😋 Promiseチェーンは時々見かける記述です♪

おわりに

皆さん、お疲れ様でした。
ここまでご覧いただき、ありがとうございました。

非同期処理の呼び出しは時代とともに書きやすく変わってきました。非同期処理そのものへの理解は必要ですが、書き方の難易度は下がってきていると思います。非同期という言葉に苦手意識を持つ方も多いかと思いますが、頑張って慣れていきましょう。

😋 これからもプログラミング学習頑張りましょう♪

参考リンク集(MDN Web Docs のリンク)

Discussion