複数の await の「順序」を一言でいうと
await を何回も使うとき、
「どこで await を書くか」によって、非同期処理が「順番に」動くか「同時に」動くかが変わります。
同じ await でも、
- 上から順に 1 個ずつ「直列」に実行する書き方
- 先に全部スタートさせておいて「並列」に進め、あとでまとめて待つ書き方
の 2 パターンがあります。
ここが重要です。
「とりあえず書いたら、全部直列になっていた」という状態になりがちです。
“本当に順番にやる必要があるのか?” “同時にやって良いのか?” を意識して、
await を置く場所を設計することが、async / await を使う上での大事な一歩です。
基本パターン①:複数 await を“順番に”実行(直列)
コードの上から下に「1つずつ」進むイメージ
まずは一番素直な書き方から。
async function run() {
console.log("A 開始");
const a = await taskA(); // A が終わるまで待つ
console.log("A 完了:", a);
console.log("B 開始");
const b = await taskB(); // B が終わるまで待つ
console.log("B 完了:", b);
console.log("C 開始");
const c = await taskC(); // C が終わるまで待つ
console.log("C 完了:", c);
console.log("全部完了");
}
JavaScriptこの場合の流れはこうです。
taskA()が始まるawaitで A が終わるまで待つ- A が終わったら B を開始
- B が終わるまで待つ
- B が終わったら C を開始
- C が終わるまで待つ
つまり、A → B → C の“完全に直列” です。
いつこの書き方が正しいか
この「1 つずつ順番に待つ」パターンが必要になるのは、
- B は A の結果を使う(A が終わらないと B を始められない)
- C は B の結果を使う
といった、「前の処理が終わらないと次に進めない」依存関係があるときです。
例えば:
async function run() {
const user = await fetchUser(); // ユーザー情報が必要
const posts = await fetchPosts(user.id); // ユーザーIDがないと投稿を取れない
const comments = await fetchComments(posts[0].id); // 投稿がないとコメントを取れない
}
JavaScriptこのような「階段状」の処理では、
すべて順番に await するのが自然です。
ここが重要です。
前後の処理に“依存関係”があるなら、素直に上から順番に await する。
「先にやらないと次が決められない」ものは、無理に並列にしようとしないのが安全です。
基本パターン②:非依存な複数 await を“同時に”走らせる(並列)
先に Promise を「貯めて」から await する
もし A と B が「お互いに関係ない処理」なら、
わざわざ A が終わるのを待ってから B を始める必要はありません。
例えば、こう書いてしまうと完全に直列です。
async function run() {
const a = await taskA(); // ここで A が終わるまで待つ
const b = await taskB(); // その後で B を始めて待つ
console.log(a, b);
}
JavaScriptこれを「並列」に動かすには、
先に Promise を作るだけ作って、あとから await します。
async function run() {
const promiseA = taskA(); // ここで A が動き始める
const promiseB = taskB(); // ここで B も動き始める
const a = await promiseA; // A の完了を待つ
const b = await promiseB; // B の完了を待つ
console.log(a, b);
}
JavaScriptこうすると、
- A と B は「ほぼ同時に」スタート
- それぞれがバックグラウンドで進む
- await に到達したときに、終わっていなければそこから待つ
という動きになります。
Promise.all を使ってもっと分かりやすく
同じことをもう少し分かりやすく書く方法が Promise.all です。
async function run() {
const [a, b] = await Promise.all([
taskA(),
taskB(),
]);
console.log(a, b);
}
JavaScriptPromise.all は、「配列の中の Promise が全部終わるまで待って、結果をまとめて返す」関数です。
ここが重要です。
複数の非同期処理が「互いに独立」しているなら、
明示的に「並列でやる」と決めてあげると、
全体の待ち時間を短くできます。
そのためのパターンが「Promise を先に作る」か「Promise.all を使う」かのどちらかです。
「順序」と「開始タイミング」は別物だという感覚
「await を書いた場所」で“開始タイミング”が変わる
よくある勘違いは、
「下に await が並んでいると、全部直列になる」
という思い込みです。
実際には、
「Promise をいつ作るか」と「いつ await するか」を分けて考えます。
例えば、次の2つを比べてみてください。
// パターンA:直列
const a = await taskA();
const b = await taskB();
// パターンB:並列
const promiseA = taskA();
const promiseB = taskB();
const a = await promiseA;
const b = await promiseB;
JavaScriptどちらも await 自体は 2 回登場しますが、
- パターンA → B は「A が終わってから始まる」
- パターンB → A と B が同時に始まる
という違いがあります。
「順番どおりに結果を使う」ことと「順番どおりに始める」ことは違う
例えば、「A と B を同時に開始して、完了した順に処理する」ことも理論上はできますが、
初心者のうちは、まずは
- 直列:上から 1 個ずつ
await - 並列:先に全部
taskX()してから、あとでawaitorPromise.all
の 2 パターンを使い分けられれば十分です。
ここが重要です。
await の“順序”という言葉で混乱しがちですが、
実際に設計すべきなのは
「いつリクエスト(Promise)を飛ばすか」と
「いつ結果を受け取るか」の 2 つ。
“開始の順番”と“待つ順番”を、少しだけ意識して切り分けてみてください。
具体例でイメージを固める
例1:ユーザー情報と設定を“同時に”取得
ユーザー情報とユーザー設定を API から取ってくる例を考えます。
ユーザー情報:
function fetchUser() {
return new Promise(resolve => {
setTimeout(() => {
resolve({ id: 1, name: "Taro" });
}, 1000); // 1秒かかる
});
}
JavaScript設定情報:
function fetchSettings() {
return new Promise(resolve => {
setTimeout(() => {
resolve({ theme: "dark" });
}, 1000); // 1秒かかる
});
}
JavaScript直列で書いた場合
async function runSerial() {
const user = await fetchUser(); // ここで1秒待つ
const settings = await fetchSettings(); // さらに1秒待つ
console.log("直列:", user, settings);
}
JavaScript合計で約 2 秒かかります。
並列で書いた場合
async function runParallel() {
const userPromise = fetchUser(); // ここでスタート(非同期)
const settingsPromise = fetchSettings(); // ここでもスタート
const user = await userPromise; // だいたい同じタイミングで終わる
const settings = await settingsPromise;
console.log("並列:", user, settings);
}
JavaScriptこちらは、
ほぼ 1 秒ちょっとで両方終わる イメージです。
もちろん、Promise.all を使ってもよいです。
async function runParallelAll() {
const [user, settings] = await Promise.all([
fetchUser(),
fetchSettings(),
]);
console.log("並列(Promise.all):", user, settings);
}
JavaScriptここが重要です。
「片方が終わるまで、もう片方を始められない理由」がなければ、
基本的には並列パターンを検討する価値があります。
ユーザー情報と設定のように“別々のAPI”は典型的な並列候補です。
例2:前の結果を使う場合は素直に順番に await
今度は、B が A の結果に依存する例です。
async function run() {
const user = await fetchUser(); // ユーザー情報
const posts = await fetchPosts(user.id); // ユーザーIDを使う
const firstPost = posts[0];
const comments = await fetchComments(firstPost.id); // 投稿IDを使う
console.log(user, posts, comments);
}
JavaScriptここでは、
fetchPosts(user.id)はuser.idがないと始められないfetchComments(firstPost.id)はpostsがないと決められない
という強い依存があります。
無理に並列にしても意味がありませんし、
バグにつながるだけです。
ここが重要です。
「上の結果を使って次を決める」タイプは、素直に順番に await する。
“並列にすれば速くなる” という発想より、
“依存関係があるかどうか” を優先して考えると、安全かつ分かりやすいコードになります。
エラーと複数 await の組み合わせ
直列の場合のエラー
async function run() {
try {
const a = await taskA(); // ここでエラーになったら…
const b = await taskB(); // ここには来ない
console.log(a, b);
} catch (err) {
console.error("どこかでエラー:", err);
}
}
JavaScriptA で失敗したら、B はそもそも実行されません。
並列(Promise.all)の場合のエラー
async function run() {
try {
const [a, b] = await Promise.all([
taskA(),
taskB(), // A か B のどちらかが失敗したら、Promise.all 全体が reject
]);
console.log(a, b);
} catch (err) {
console.error("どれか一つでもエラー:", err);
}
}
JavaScriptPromise.all の場合、
- A と B のどちらか一つでも失敗 → 全体が失敗
- 両方とも成功 → [a, b] が返る
という動きになります。
ここが重要です。
「片方が失敗しても、もう片方の結果は欲しい」ような場合は、
Promise.all よりも個別に try / catch する、あるいは Promise.allSettled を使う、など別の設計が必要になります。
“並列にした結果、エラーの扱いがどう変わるか” もセットで考えるのが大事です。
初心者として「複数 await の順序」で本当に押さえてほしいこと
上から順に await を書くと、その部分は基本的に「直列実行」になる。
A の await が終わってからでないと B は始まらない。
依存関係がない複数の非同期処理は、
先に taskX() を全部呼び出して Promise を作っておき、
あとでまとめて await したり Promise.all したりすることで「並列実行」できる。
「いつ“始めるか”」と「いつ“待つか”」は別。await を書いた場所で“待つタイミング”が決まり、taskX() を呼んだ瞬間に“処理は開始”している。
前の結果を使う処理(ID を使って次の API を呼ぶ、など)は、素直に順番に await するのが正しい。
無理に並列にしようとしない。
ここが重要です。
複数 await を書くときは、
“これは本当に順番にやる必要があるか?”
“同時に進めて、あとでまとめて待てるのか?”
を一度立ち止まって考えてみてください。
その 1 回の思考の差が、「動くけど遅いコード」と「速くて分かりやすいコード」の分かれ目になります。
もし手を動かして練習したくなったら、こんな課題をやってみてください。
// 1. 1秒後に "A" を返す taskA、1秒後に "B" を返す taskB を作る。
// 2. それぞれを「直列で await する関数」と「並列で await する関数」を書いて、
// 所要時間の違いを console.time などで測ってみる。
// 3. 「どんなときに直列が必要で、どんなときに並列が使えるのか」を
// 自分の言葉でコメントに書いてみる。
JavaScript「待っている時間」と「実際にやっている仕事」のイメージが結びついてくると、
await の“順序”を自分でコントロールできる感覚がぐっと強くなっていきます。
