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?

【ジュニアエンジニアの手帖#2】非同期処理の本質は「時間」ではなく「制御系の切り替え」だった話

Posted at

はじめに

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」から一歩進むきっかけになれば幸いです。

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?