JavaScript

Promise.all()で複数APIを並列実行する方法|JavaScript入門

JavaScriptのPromise.all()を使い、複数APIへのリクエストをまとめて処理する基本と、実務で注意したいエラー処理や使い分けを解説します。

この記事でわかること

  • Promise.all()で複数の非同期処理をまとめて待機する方法がわかる
  • 逐次実行とまとめて実行する場合の違いを理解できる
  • 一部のAPIが失敗した場合の扱いと実務上の注意点を把握できる

Promise.all()で複数APIを処理する基本

画面表示にユーザー情報、商品情報、通知情報などが必要な場合、それぞれを別のAPIから取得することがあります。これらに依存関係がなければ、1つずつ完了を待つのではなく、同じタイミングでリクエストを開始できます。Promise.all()は複数のPromiseを受け取り、すべてが成功するまで待機します。成功すると、結果を入力した順番の配列で取得できます。ここでいう「並列」は、JavaScriptの独立した非同期処理を同時期に開始するという意味で使います。

APIを順番に呼ぶ場合とPromise.all()を使う場合の違い

JavaScript
async function getDataSequentially() {
  const userResponse = await fetch('/api/user');
  const user = await userResponse.json();

  const ordersResponse = await fetch('/api/orders');
  const orders = await ordersResponse.json();

  return { user, orders };
}

// 期待される挙動:
// userの取得が終わってからordersの取得を開始する。
JavaScript
async function getDataTogether() {
  const [userResponse, ordersResponse] = await Promise.all([
    fetch('/api/user'),
    fetch('/api/orders'),
  ]);

  const [user, orders] = await Promise.all([
    userResponse.json(),
    ordersResponse.json(),
  ]);

  return { user, orders };
}

// 期待される挙動:
// userとordersのリクエストを同時期に開始する。
// 実際の所要時間はネットワークやサーバーの状態に左右される。
観点逐次処理Promise.all()
開始タイミング前の処理が終わってから開始複数の処理を同時期に開始
待ち時間各処理の時間が積み上がりやすい独立した処理では短くなる場合がある
依存関係前の結果を次で利用しやすい処理同士が独立している場合に適する
失敗時処理ごとに制御しやすいいずれかのPromiseが拒否されると全体が拒否される
逐次処理とPromise.all()の違い

後続のAPIが前のAPIの結果を必要とする場合は、単純にPromise.all()へまとめられません。その場合は、必要な処理を順番に実行します。Promise.all()を使うと、独立した処理を待ち合わせる構成に向いています。

Promise.all()で複数APIを実装する

実務では、fetch()でレスポンスを取得し、HTTPステータスを確認してからJSONへ変換する形が基本です。fetch()は404などのHTTPエラーだけではrejectされないため、Response.okを確認して必要に応じてエラーを発生させます。

JavaScript
async function loadPageData() {
  const fetchJson = async (url) => {
    const response = await fetch(url);

    if (!response.ok) {
      throw new Error(`HTTP error: ${response.status}`);
    }

    return response.json();
  };

  const [user, orders] = await Promise.all([
    fetchJson('/api/user'),
    fetchJson('/api/orders'),
  ]);

  // userとordersがそろってから画面表示などに利用する。
  return { user, orders };
}

// URL、認証方式、レスポンス形式は実際のAPI仕様に合わせて変更する。

結果配列の順番は、処理が完了した順番ではなく、Promise.all()へ渡した順番に対応します。上の例では、1番目がuser、2番目がordersです。

複数APIのエラーを処理する方法

JavaScript
async function loadData() {
  try {
    const [user, notifications] = await Promise.all([
      fetchJson('/api/user'),
      fetchJson('/api/notifications'),
    ]);

    return { user, notifications };
  } catch (error) {
    // どれか1つが失敗した場合の処理
    console.error('APIの取得に失敗しました', error);
    throw error;
  }
}

async function fetchJson(url) {
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(`HTTP error: ${response.status}`);
  }
  return response.json();
}

// 期待される挙動:
// いずれかの取得に失敗するとcatchへ進む。

Promise.all()では、まとめたPromiseのいずれかが拒否されると、戻り値のPromiseも拒否されます。ただし、すでに開始された他の非同期処理まで自動的に停止するわけではありません。一部のAPIが失敗しても取得できたデータを使いたい場合は、個別にcatchする方法やPromise.allSettled()を検討します。Promise.allSettled()は、すべての処理が成功または失敗するまで待機し、各結果を個別に確認できます。

開発現場での使いどころと注意点

  • 同時に取得して問題がない、互いに独立したAPIに使う。
  • 大量のリクエストを一度に開始すると、API提供元やネットワークへの負荷になる可能性があるため、件数制御や段階的な取得を検討する。
  • 認証トークン、タイムアウト、キャンセル、ログ出力はPromise.all()とは別に設計する。
  • 同じデータを繰り返し取得しないよう、キャッシュや取得タイミングも確認する。
方法向いている場面成功・失敗時の扱い
Promise.all()すべて成功してから処理したい1つでも拒否されると全体が拒否される
逐次処理前の結果を次の処理で使う処理ごとに順番やエラーを制御しやすい
Promise.allSettled()成功した処理と失敗した処理を個別に確認したいすべての処理完了後に各結果を確認する
非同期処理メソッドの使い分け

参考