Promise.all()で複数APIを処理する基本
画面表示にユーザー情報、商品情報、通知情報などが必要な場合、それぞれを別のAPIから取得することがあります。これらに依存関係がなければ、1つずつ完了を待つのではなく、同じタイミングでリクエストを開始できます。Promise.all()は複数のPromiseを受け取り、すべてが成功するまで待機します。成功すると、結果を入力した順番の配列で取得できます。ここでいう「並列」は、JavaScriptの独立した非同期処理を同時期に開始するという意味で使います。
APIを順番に呼ぶ場合とPromise.all()を使う場合の違い
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の取得を開始する。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が拒否されると全体が拒否される |
後続のAPIが前のAPIの結果を必要とする場合は、単純にPromise.all()へまとめられません。その場合は、必要な処理を順番に実行します。Promise.all()を使うと、独立した処理を待ち合わせる構成に向いています。
Promise.all()で複数APIを実装する
実務では、fetch()でレスポンスを取得し、HTTPステータスを確認してからJSONへ変換する形が基本です。fetch()は404などのHTTPエラーだけではrejectされないため、Response.okを確認して必要に応じてエラーを発生させます。
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のエラーを処理する方法
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() | 成功した処理と失敗した処理を個別に確認したい | すべての処理完了後に各結果を確認する |