はじめに
JavaScriptで fetch()
を使って非同期処理をしていたとき、こんな疑問が浮かびました。
「fetchって非同期処理って言うけど、内部ではどのような処理が行われているのだろう?」
この疑問から少しずつ調べていくうちに、JavaScriptとブラウザの関係がはっきりと見えてきました。この記事では、その学びを振り返りながら、非同期処理の仕組みとブラウザとの関係について整理していきます。
Promiseの仕組みを整理
JavaScriptの非同期処理は主に Promise を通して扱われます。
非同期処理を開始すると Promise が返され、.then()
を使って後続の処理を登録します。
その後、非同期処理が完了すると resolve()
が呼ばれ、
その時点でマイクロタスクキューに .then()
に登録された関数が入れられ、コールスタックが空になったタイミングで実行されます。
ここで疑問が湧きました。
「そもそも Promise の中の
resolve()
って誰が呼んでるの?」
非同期処理は JavaScript がやっていない
fetch()
はJavaScript自体が提供している関数ではなく、
ブラウザの中にある「Web API」という仕組みが提供しているものです。
Web APIとは?
- ブラウザがJavaScriptに提供している外部機能
- JavaScript単体では実行できないような処理(通信、タイマーなど)を代わりに実行してくれる
- 代表例:
fetch
,setTimeout
,addEventListener
,localStorage
,geolocation
など
つまり、役割は以下のように分担されている:
やること | 担当者 |
---|---|
通信処理 | ブラウザ(Web API) |
Promise作成と.then() の登録 |
JavaScriptエンジン(例:V8) |
処理の実行順制御 | イベントループ |
非同期処理の流れ
1. JavaScriptが fetch() を呼び出す
→ Promiseを返す(この時点ではまだ結果はない)
2. ブラウザのWeb APIが通信処理を開始する
3. JavaScriptは次の処理へ進む(ブロッキングされない)
4. 通信が完了すると、Web APIはPromise作成時に渡されていた resolve() を呼び出す
5. resolve() が Promise の状態を「完了」に変更し、登録されていた .then() の関数をマイクロタスクキューに追加する
6. コールスタックが空になったタイミングでマイクロタスクが実行される
ここで重要なのは、resolve()
は JavaScript の Promise 内部で用意された関数であり、Web API は「処理が終わったらこの関数を呼んでね」と渡された resolve()
を呼んでいるだけ。
その結果、Promiseの状態が「fulfilled」になり、.then()
に登録された関数がJavaScriptエンジンによってマイクロタスクキューに追加される。
なぜJavaScriptは直接処理をしないのか?
「サンドボックス」というセキュリティ上の制限があるため。
サンドボックスとは、JavaScriptが動作する安全な実行空間のこと。Webページ上で動くJavaScriptがユーザーのファイルやネットワーク、プロセスに勝手にアクセスすることを防ぐため、ブラウザはJavaScriptにできることを制限している。
そのため、ネットワーク通信などの危険な処理は、JavaScriptではなくブラウザが担っている。
上記について、サンドボックスはセキュリティのためではあるが、fetch 関数が存在することとは直接関係はない。制限されているから fetch を使う、というよりも、そもそも fetch を使うように設計されている。(コメントいただき、修正しました。)
気づいたこと:これはOSとアプリの関係に似ている
ここまで学んでいく中で、次のように感じました。
「JavaScriptとブラウザの関係は、アプリとOSの関係に似ている」
多くのプログラミング言語(C, Goなど)はOSのシステムコールを使ってファイルや通信を扱います。
同様に、JavaScriptはブラウザが提供するWeb APIを使って外部の処理を行っています。
まとめ:非同期処理はブラウザとの共同作業
-
fetch()
で非同期処理ができるのは、ブラウザのWeb APIが裏で通信処理を行ってくれているから - JavaScriptはPromiseを作り、処理の登録をするだけで、実際の処理はブラウザに任せている
-
resolve()
はPromiseの作成時にJavaScriptエンジンが生成する関数であり、それをWeb APIに「後で呼んでね」と渡している -
resolve()
が呼ばれたあとはJavaScriptエンジンが.then()
に登録された処理をマイクロタスクキューに追加し、イベントループによって実行される