はじめに
JavaScript の async / await について、
- 「非同期処理」
- 「未来の値」
- 「待つ / 待たない」
といった説明をよく見かけます。
しかし実際に挙動を追っていくと、
これらの説明には どうしても違和感 が残りました。
この記事では、async / await / Promise を
「時間」ではなく「制御系(処理系)」の観点 から整理します。
async は本体ではない
まず結論から。
async function f() {
return 9;
}
これを実行すると:
console.log(f());
// Promise { 9 }
ここで重要なのは、
async は「非同期にする構文」ではない
という点です。
async の役割はただ一つ:
「この関数は Promise を返します」という契約
await が一切なければ、
中身は 完全に同期的に実行 され、
Promise は 即 resolve されます。
本体は await
では、何が処理を「非同期っぽく」しているのか。
答えは await です。
async function f() {
console.log("A");
await sleep(1000);
console.log("B");
}
このとき起きているのは:
-
"A"は同期的に実行される -
awaitに到達した瞬間、- 現在のコールスタックは終了
- 「続き(B)」が Promise 側に登録される
- イベントループに制御が返る
- Promise が resolve された後、別の制御系で再開
つまり:
awaitは「待つ」構文ではなく、
コールスタックから Promise 制御系へ
処理の継続を委譲するスイッチ
Promise は「値の箱」ではない
Promise を「未来の値の箱」と説明することがありますが、
これは 半分正しくて、半分誤解を生みます。
Promise の本質は:
処理の状態と、処理の継続(制御)を表現するオブジェクト
です。
だから:
console.log(async_process());
の結果が
Promise { <pending> }
になるのは、
- 値が「まだ来ていない」からではなく
- 処理の続きが別の制御系に委ねられているから
です。
同期処理に「値は戻らない」
よくある疑問:
非同期処理の結果を、
一番上の同期処理で使えないのはなぜ?
答えは単純で、
同期処理に戻った時点で、
非同期処理の結果は
まだ存在しないか、
すでに別の制御系で消費されている
からです。
JavaScript には、
- 「途中で止めて保持する処理」
- 「同期の世界で待つ」
という概念は存在しません。
あるのは:
- 処理を一度終わらせる
- 続きだけを別の制御系に登録する
というモデルです。
fork / wait との類比
このモデルは、shell の fork / wait によく似ています。
| shell | JavaScript |
|---|---|
| fork | Promise 境界 |
| 子プロセス | Promise チェーン |
| wait | await |
| 親が進む | 同期処理が続行 |
ただし決定的な違いがあります。
-
forkは 世界(プロセス)を分ける - Promise は 世界を分けず、制御だけを分ける
Promise は非常に軽量で、
- 制御を大量に増殖できる
- fire-and-forget が可能
- 親が回収しなくても進む
その代わり、
責任はすべて開発者側 にあります。
async 関数の2つの用途
ここまで整理すると、
async 関数には明確に 2種類の役割 があると分かります。
① 値を返す async
async function getData() {
return await fetchData();
}
- 呼び出し側が
awaitする - 値を受け取る責任がある
② 処理を投げる async(fire-and-forget)
async function runTask() {
await sleep(1000);
console.log("done");
}
- 値は返さない
- 非同期側で処理が完結する
この設計を意識しないと、
- Promise が迷子になる
- エラーが伝播しない
- 順序バグが出る
まとめ
- async は Promise を返す契約
- 本体は await
- await は「待つ」ではなく「制御移譲」
- Promise は「値の箱」ではなく「制御の表現」
- 非同期は時間の話ではなく、処理系の切り替え
- 同期処理に非同期の値は戻らない
- Promise は fork に似ているが、制御だけを分ける
おわりに
async / await は便利な構文ですが、
雰囲気で使うと事故ります。
一方で、
制御モデルとして理解すると一気に腑に落ちる。
この記事が、
「await 付けとけばOK」から一歩進むきっかけになれば幸いです。