1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

async/await と Promise(then) の違いを、出力順で比べてみた

1
Last updated at Posted at 2026-10-18

はじめに

筆者は、プログラミングスクールで JavaScript の非同期処理を学んでおり、その中である疑問にぶつかりました。

Promise のあとに async/await を学んで、「Promise をシンプルに書ける構文」という説明は理解できたのですが、await が何を止めているのか?という疑問です。
そこがあいまいなままでした。

なんとなく「await は任意のタイミングで処理を止めてくれて、デバッグしやすいのだろう」という認識でいたのですが、それではモヤモヤが残るので、ちゃんと理解したいと思い、今回の記事を書こうとなりました!📝

自分で小さなコードを書いて、console.log の出力順を1行ずつ追ってみました。
この記事では、その結果をもとに

  • await は何を止めているのか
  • Promise(then)で書いた場合と、何が違うのか

を整理していきます!🐾

本記事は、学習内容の整理と文章の構成に AI(Claude)を利用しました。掲載しているコードは手元で動作を確認しています。

対象読者

  • Promise と async/await を一通り学んだけど、動きのイメージがあいまいな人
  • 「await で待つ」の「待つ」が何を指しているのか、しっくりきていない人

結論

await で一時停止するのは、その async 関数の中だけ。
関数の外のコードは待たずに先へ進むので、待っている間に画面が固まることはありません。

そして、Promise(then)との一番の違いは次の点です。

async/await では、関数の中で書いた順番 = 実行される順番 になる。

1. 準備:指定した時間だけ待つ関数

この記事では「お茶を淹れる」流れを例にします。まず、時間のかかる作業(お湯を沸かす、など)の代わりに、指定した時間だけ待つ関数を用意します。

tea.js
function wait(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

wait(3000) と呼ぶと、「3秒後に成功する Promise」が返ってきます。

setTimeout(resolve, ms) は「ms ミリ秒たったら resolve を呼んで」という意味です。resolve() ではなく resolve と書いているので、今すぐ呼ぶのではなく、関数そのものをタイマーに渡しています。

2. 実験:出力順を見てみる

console.log の先頭に付けた番号は、実行される順番です。書いてある順番と見比べてみてください。

tea.js
async function makeTea() {
  console.log("1. お湯を沸かし始める");
  await wait(3000); // お湯が沸くまで3秒待つ
  console.log("3. お湯が沸いた");
  console.log("4. お茶を淹れる");
}

makeTea();
console.log("2. 待っている間に、湯のみを用意する");
コンソール
1. お湯を沸かし始める
2. 待っている間に、湯のみを用意する
3. お湯が沸いた
4. お茶を淹れる

「2.」はコードの一番最後に書いてあるのに、2番目に出ています。

もし await がプログラム全体を止めるなら、「2.」は3秒待ってから、一番最後に出るはずです。先に出ているということは、止まっていたのは makeTea の中だけだった、ということになります。

3. 何が起きているのか

時間の流れに沿って図にすると、こうなります。

await に来た関数は、そこに「しおり」を挟んで、いったん呼び出し元に戻ります。Promise が成功したら、しおりの位置から続きを再開します。

しおりを挟んでいる間も、関数の外のコード(湯のみの用意)は普通に進みます。だから、待っている間に画面が固まることはありません。

await は、非同期処理を「同期処理に戻す」ものではありません。止まるのは async 関数の中だけで、JavaScript 全体は動き続けています。

最初は「await = 好きなところで処理を止める(デバッグ用の)機能」だと思っていましたが、実際には「Promise の結果が出るまで、その関数の中だけ続きを待つ」仕組みでした。処理を一時停止して変数の中身を確認したいときに使うのは、await ではなくデバッガーのブレークポイント(または debugger; 文)です。

4. Promise(then)で書くとどうなるか

同じ流れを then で書いてみます。今度は「湯のみを用意する」を、関数の中に書きました。

tea.js
function makeTea() {
  console.log("1. お湯を沸かし始める");

  wait(3000).then(() => {
    console.log("3. お湯が沸いた");
    console.log("4. お茶を淹れる");
  });

  console.log("2. (関数の中)湯のみを用意する");
}

makeTea();
コンソール
1. お湯を沸かし始める
2. (関数の中)湯のみを用意する
3. お湯が沸いた
4. お茶を淹れる

関数の中に書いた「2.」が、then の中身より先に出ています。

違いは「どこまでが待ったあとの処理か」

書き方 「待ったあと」に実行されるのは
then then に渡した関数の { } の中だけ
async/await await より下に書いたすべて
  • then: .then(...) は「終わったらこれをして」と予約するだけです。予約したらすぐ次の行に進むので、then の外に書いた行は待たずに実行されます
  • async/await: await で関数そのものが一時停止するので、その下に書いた行は全部「待ったあと」になります

だから async/await では、書いた順番 = 実行される順番 になり、上から順に読むだけで動きが分かります。

一方で、関数の外から見ると、どちらも同じです。呼び出したらすぐに戻ってきて、外のコードは待たずに進みます。

5. 結果を受け取るときの違い

今度は、Promise が成功したときに値を渡してくれる関数を使います。

tea.js
function boilWater() {
  return new Promise((resolve) => {
    setTimeout(() => resolve("沸かしたお湯"), 3000);
  });
}

then の場合:引数で受け取る

boilWater().then((water) => {
  console.log(`${water}でお茶を淹れる`);
});

async/await の場合:変数で受け取る

async function makeTea() {
  const water = await boilWater();
  console.log(`${water}でお茶を淹れる`);
}
コンソール(どちらも同じ)
沸かしたお湯でお茶を淹れる

resolve("沸かしたお湯") に渡した値が、then では引数 water に、async/await では変数 water に入ります。

結果が2つ以上になると、差が大きくなる

「お湯」と「茶葉」の両方を最後に使いたい場合を考えます。

// then の場合:water は最初の then の中でしか使えない
let water;
boilWater()
  .then((result) => {
    water = result;          // 外の変数に入れておく必要がある
    return getTeaLeaves(true); // 茶葉を取る(6章で定義)
  })
  .then((leaves) => {
    console.log(`${water}と${leaves}でお茶を淹れる`);
  });

// async/await の場合:普通の変数なので、そのまま使える
async function makeTea() {
  const water = await boilWater();
  const leaves = await getTeaLeaves(true);
  console.log(`${water}と${leaves}でお茶を淹れる`);
}

then の引数は、その { } の中でしか使えません。async/await なら、同じ関数の中の普通の変数として、下の行のどこでも使えます。

6. エラー処理の違い

茶葉が切れていたら失敗する関数を用意します。

tea.js
function getTeaLeaves(hasLeaves) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (hasLeaves) {
        resolve("緑茶の茶葉");
      } else {
        reject(new Error("茶葉が切れていた"));
      }
    }, 1000);
  });
}

then の場合:.catch / .finally をつなぐ

getTeaLeaves(false)
  .then((leaves) => console.log(`${leaves}でお茶を淹れる`))
  .catch((error) => console.log(`${error.message}ので、麦茶にする`))
  .finally(() => console.log("やかんの火を止める"));

async/await の場合:try / catch / finally で囲む

async function brew(hasLeaves) {
  try {
    const leaves = await getTeaLeaves(hasLeaves); // 失敗したら、ここから catch へジャンプ
    console.log(`${leaves}でお茶を淹れる`);
  } catch (error) {
    console.log(`${error.message}ので、麦茶にする`);
  } finally {
    console.log("やかんの火を止める");          // 成功でも失敗でも必ず
  }
}

brew(true);
brew(false);
コンソール
緑茶の茶葉でお茶を淹れる
やかんの火を止める
茶葉が切れていたので、麦茶にする
やかんの火を止める
役割 then async/await
成功したとき .then(...) try の中の await の下
失敗したとき .catch(...) catch (error) { }
どちらでも最後に .finally(...) finally { }

try / catch は、await の失敗だけでなく、try の中で起きた普通のエラー(存在しないメソッドを呼んだ、など)もまとめて捕まえられます。

7. 順番に待つか、同時に待つか

「お湯を沸かす(3秒)」と「湯のみを温める(2秒)」は、お互いに関係ない作業です。

tea.js
function warmCup() {
  return new Promise((resolve) => {
    setTimeout(() => resolve("温めた湯のみ"), 2000);
  });
}

await を順番に並べると、合計時間がかかる

async function sequential() {
  const start = Date.now();
  await boilWater(); // 3秒
  await warmCup();   // 2秒(お湯が沸いてから始まる)
  console.log(`順番に待つ: 約${Math.round((Date.now() - start) / 1000)}秒`);
}

Promise.all を使うと、同時に進む

async function parallel() {
  const start = Date.now();
  const [water, cup] = await Promise.all([boilWater(), warmCup()]);
  console.log(`同時に待つ: 約${Math.round((Date.now() - start) / 1000)}秒`);
}
コンソール
順番に待つ: 約5秒
同時に待つ: 約3秒

await を順番に並べると、1つ終わるまで次が始まらないので、3 + 2 = 5秒かかります。Promise.all の中で2つを続けて呼ぶと、両方のタイマーがほぼ同時に動き出し、それをまとめて待つので、長いほうの3秒で済みます。

状況 書き方
前の結果を次で使う(お湯が沸いてから、お茶を淹れる) await を順番に並べる
お互いに関係ない(お湯を沸かす / 湯のみを温める) await Promise.all([...]) で同時に

async/await は「順番に待つ」のが得意ですが、「同時に待つ」ときは Promise の機能(Promise.all)をそのまま使います。async/await を使っていても、Promise の知識は必要だということです。

8. Promise(then)との違いまとめ

観点 then async/await
中身 Promise 同じ Promise
「待ったあと」の範囲 then の { } の中だけ await より下の全部
書いた順と実行順 ずれることがある 関数の中では一致する
結果の受け取り 引数(その { } の中だけ) 変数(関数の中のどこでも)
エラー処理 .catch / .finally try / catch / finally
同時に待つ Promise.all(...).then(...) await Promise.all(...)
関数の外から見た動き 待たずに進む 待たずに進む(同じ)

できることは同じで、違うのは書き方と読みやすさです。

おまけ:「止まるのは関数の中だけ」と分かると理解できること

await を付け忘れると、Promise がそのまま入る

async function makeTea() {
  const water = boilWater(); // await を付け忘れた
  console.log(water);        // Promise {<pending>}
}

await がないと関数は止まらないので、まだ結果が出ていない Promise がそのまま変数に入ります。

async 関数の戻り値は、必ず Promise になる

async function getTea() {
  return "緑茶";
}

console.log(getTea()); // Promise {<fulfilled>: '緑茶'}

async 関数は、await を使っているかどうかに関係なく、必ず Promise を返す決まりになっています。途中で一時停止して呼び出し元に戻ることがあるので、結果はあとから Promise で受け取る、と考えると覚えやすいです。中身を使うには、呼ぶ側でも await するか .then で受け取ります。

まとめ

  • await で一時停止するのは、その async 関数の中だけ。関数の外は待たずに進むので、待っている間に画面は固まらない
  • async/await では、関数の中で書いた順番 = 実行される順番になる。then では then の中だけが「あと」で実行される
  • 結果は普通の変数で受け取れて、エラーは try / catch で受けられる。同時に待つときは Promise.all を使う

最初は「await = 処理を止める機能」くらいの理解でしたが、出力順を1行ずつ追ってみたことで、「await で待つ」の意味がはっきりしました。

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?