awaitを使った非同期処理の制御と実践例をわかりやすく解説

スポンサーリンク
スポンサーリンク
amazon
スマイルSALE
--:--:--
ad. 価格範囲を指定して商品を探せます

awaitの概要

非同期処理の制御 JavaScript予約語

await

概要 awaitは、JavaScriptの非同期処理(Promise)を同期処理(上から下へ進む普通の処理)のように簡潔に記述するために使用されるキーワードで、async関数内でのみ使用できます。

わかりやすく説明 await「この非同期処理が完了して結果が出るまで、次の行に進まずにその場で待ってて!」という指示をJavaScriptに出す役割を持っています。

  • awaitPromiseの結果(解決=成功、または拒否=失敗)が確定するまで、その関数の非同期処理を一時停止します。
  • これまで.then()のコールバック関数を使わなければ受け取れなかった処理の完了結果を、通常の変数への代入と同じ形で直接取得することができます。
  • 基本ルールとして、asyncキーワードがついた関数の中でしか使用できません。

awaitの基本的な使い方

JavaScriptの標準的な関数 fetch などをそのまま使うと、データの取得を待たずに次の行のプログラムが実行されてしまい、エラーの原因になります。
そこで await を使用することで、処理の完了を確実に待ってから次のステップへ進めるようになります。
以下は、非同期処理の完了を待ち、その結果を次の処理に使う基本的な例です。

// awaitを使用した基本例
async function fetchData() {
    // 1. サーバーからデータを取得し終わるまで、この行で処理を一時停止して待機します
    const response = await fetch("https://jsonplaceholder.typicode.com/posts/1"); 
    
    // 2. 取得したレスポンスをJSON形式のデータに解析し終わるまで、さらに待機します
    const data = await response.json(); 
    
    // 3. すべての準備が整ったデータが変数dataに入ったので、コンソールに出力します
    console.log(data); 
}

// 関数の実行
fetchData();
  • fetchによるネットワーク通信の完了をawaitが待機します。
    これにより、データがまだ届いていないのに次の処理が進んでしまうバグを防ぎます。
  • 処理が終了すると、Promiseの解決結果(Responseオブジェクトや解析済みデータ)がそのまま左辺の変数(responsedata)として直感的に受け取れます。

複数のawaitを使用した処理の連続実行

複数の非同期処理に依存関係がある場合(例:1つ目の通信で取得した「ユーザーID」を使って、2つ目の通信で「そのユーザーの詳細情報」を取得する、など)は、awaitを順番に並べることで、処理を直列的に(1つずつ確実にステップを踏んで)実行させることができます。

// 直列的な非同期処理(1つずつ順番に実行)
async function processSequential() {
    // 1つ目の通信が完全に完了して、データ1が確定するまで最大で数秒待ちます
    const data1 = await fetch("https://jsonplaceholder.typicode.com/posts/1").then(res => res.json());
    console.log("データ1を取得しました:", data1);

    // 1つ目の処理がすべて終わった「後」で、初めて2つ目の通信を開始し、完了するまで再び待ちます
    const data2 = await fetch("https://jsonplaceholder.typicode.com/posts/2").then(res => res.json());
    console.log("データ2を取得しました:", data2);
}

processSequential();
  • 前の処理が完全に完了してから次の非同期処理を開始するため、プログラムのタイムラインが人間の直感と同じになり、処理の順序が重要な場合に最適な書き方です。
  • ただし、お互いの処理に関係がない(独立している)場合は、待ち時間が累積して無駄になってしまう性質もあります。

非同期処理の並列実行

複数の非同期処理がそれぞれ独立しており、順番を待つ必要がない場合は、Promise.all を活用します。
await fetch(...) をただ並べると「1つ目が終わるまで2つ目が始まらない」ため遅くなりますが、Promise.all を使えばすべての通信を一斉にスタート(並列実行)させることができ、全体の処理時間を大幅に短縮できます。

// 並列実行の例(複数同時に実行して時間を短縮)
async function processParallel() {
    // 2つの通信を同時にスタートさせ、両方の結果が出揃うまで「まとめて」1回だけawaitで待ちます
    const [data1, data2] = await Promise.all([
        fetch("https://jsonplaceholder.typicode.com/posts/1").then(res => res.json()),
        fetch("https://jsonplaceholder.typicode.com/posts/2").then(res => res.json())
    ]);

    // 両方の通信が完了した状態で、それぞれのデータにアクセスできます
    console.log("データ1:", data1);
    console.log("データ2:", data2);
}

processParallel();
  • Promise.allに配列として複数の非同期処理を渡すことで、ブラウザはそれらを同時にバックグラウンドで一斉実行します。
  • 全ての処理が完了したタイミングでawaitの待機が解け、結果を配列の分割代入([data1, data2])を使ってきれいに、まとめて受け取ることができます。

例外処理との組み合わせ

ネットワーク通信には、相手のサーバーが落ちていたり、URLが間違っていたり、ユーザーの電波が途切れたりといった「予期せぬ失敗(エラー)」がつきものです。
async/await を使う最大のメリットの1つは、通常のJavaScriptと同じ使い慣れた try...catch 構文を使って、これら非同期処理の例外をスマートにキャッチできる点にあります。

// 非同期処理の例外処理
async function fetchDataWithErrorHandling() {
    try {
        // tryブロックの中に、失敗する可能性のある非同期処理を記述します
        const response = await fetch("https://invalid.url"); // 無効なURL(ここでエラーが発生)
        
        // 【実務のポイント】fetchは通信自体が成功すると404や500エラーでもcatchに飛ばないため、
        // 明示的に response.ok(ステータスが200〜299か)をチェックしてエラーを発生させます
        if (!response.ok) {
            throw new Error(`HTTPエラー: ${response.status}`);
        }
        
        const data = await response.json();
        console.log(data);
    } catch (error) {
        // 上記のtry内で通信が失敗した場合や、throwされたエラーはすべて自動的にこのcatchブロックに飛んできます
        console.error("エラーが発生しました。処理を中断します:", error.message);
    }
}

fetchDataWithErrorHandling();
  • try...catchを活用することで、エラーが起きた際にプログラム全体がクラッシュして画面が真っ白になるのを防ぎます。
  • エラーメッセージを画面に分かりやすく表示したり、エラーログをサーバーに送信したりする「ユーザーに優しいエラーハンドリング」を簡単に実装できます。

注意点

  • async関数内でのみ使用可能: awaitは通常の関数(あるいは関数の外)に単体で記述すると、JavaScriptが解釈できず構文エラー(SyntaxError)になります。
    必ずセットとなるasync関数の中で使いましょう。
// ❌ NGな例:通常の関数(asyncがない)の中で使うと構文エラーになります
function badExample() {
    const response = await fetch("https://api.example.com"); // SyntaxError: await is only valid in async functions
}
  • 不必要な直列実行(お留守番状態)に注意: 順番を待つ必要がない独立した処理に対して、癖で await を何行もただ並べてしまうと、前の通信が終わるまで次の通信が始まらないため、アプリの挙動が不必要に遅くなります。
    並列にできるものは Promise.all を積極的に検討してください。
  • Promise.allでのエラーの巻き込み: Promise.allは「一蓮托生(どれか1つでも失敗したらすべて失敗)」という強い性質を持っています。
    1つの通信エラーによって、他の成功していた通信結果まで巻き添えで破棄されてしまうため、エラーの影響範囲を考慮した実装が必要です。

よくある質問

Q: awaitはJavaScriptの処理(スレッド)を完全に止めてしまいますか?(画面がフリーズしませんか?)
A: いいえ、ブラウザ全体の動きを止める(画面をフリーズさせる)ことは絶対にありません。
awaitが一時停止させるのは、あくまでそのasync関数の「内部の進行」だけです。
その関数の外側にある他のプログラムや、ユーザーのボタンクリック、画面のスクロール・アニメーションといった処理は、裏側で別のタイムラインとしてノンブロッキング(妨げられることなく)に動き続けます。
Q: 非同期処理ではない普通の処理(数値計算や配列操作など)にも念のためawaitを使用すべきですか?
A: いいえ、使用すべきではありません。
awaitはあくまでPromiseオブジェクト(非同期処理の約束手形)に対して使うものです。
普通の処理(同期処理)の前に付けてもエラーにはならず、自動的にPromiseにラップされて動きますが、JavaScriptの内部で余計な処理コスト(マイクロタスクの生成)が発生し、パフォーマンスが低下します。
また、他の開発者がコードを見た際に「これは非同期処理なのか?」と混乱するため、非同期処理以外には付けないのが鉄則です。

// ❌ 意味のないawaitの例(動きますが、無駄な処理コストがかかり可読性も下がるため非推奨です)
async function calculate() {
    const a = 10;
    const b = 20;
    
    // ただの足し算(一瞬で終わる同期処理)に await を付ける意味はありません
    const result = await (a + b); 
    console.log(result);
}

まとめ

awaitは、複雑でネスト(深く階層化)しがちだった非同期処理(Promise)を、まるで上から下に順番に実行される普通のプログラムのようにスッキリと読みやすくしてくれる強力なツールです。

  • データの依存関係を意識して、直列(順番に実行)並列(一斉に実行)を適切に使い分けましょう。
  • ネットワーク通信をはじめとする非同期処理にエラーはつきものです。
    必ずtry...catchを活用して適切なエラーハンドリングを実装しましょう。
  • async/awaitの仕組みをマスターすることで、JavaScriptの非同期コードの可読性とメンテナンス性は劇的に向上します。