JavaScriptで非同期処理周辺のことを調べる中で出会ったイベントループの基礎知識を簡単にまとめました。

JavaScriptはシングルスレッドです。一つの作業が終わったら次の作業に移る、一度に1つのタスクしか実行できない。しかし、例えば何秒もかかる処理を待って次に進むように作業が進んでいたら画面は固まってしょうがないですし、ユーザーにとってかなりのストレスです。JavaScriptはどのように同時実行を実現させているのか。そこで出てくるのがイベントループの概念です。

非同期処理とは

非同期処理とは、あるタスクを実行している最中にその処理を止めることなく別の処理を実行すること。

イベントループの基礎知識

ブラウザ環境を理解すると下記の図のようになります。

JavaScriptエンジン

JavaScriptのコードを実行するコンピュータプログラム。
・メモリヒープ
・コールスタック

コールスタックは実行される関数を管理している場所。関数が呼び出されたら、コールスタックにどんどん積まれていって、LIFO(後入れ先出し)の原則に基づいて実行される。
(メモリヒープについては割愛します)

Web API

ブラウザが提供する機能でDOM Events, setTimeout, HTTPrequestなどの実装が含まれる。呼び出しスタックがWeb API関数に遭遇すると、それはWeb APIに渡されそこで実行される。

Task Queue

Web APIで処理が完了したものは、タスクキューという場所に追加され、コールスタックが空になるまで待機。FIFO(先入れ先出し)の原則に基づいて実行される。

Event Loop

イベントループはコールスタックとタスクキューを監視していて、コールスタックが空になったら、キューから最初のイベントをコールスタックにプッシュし実行する。

ここら辺の動きについては、言葉だけでは分かりづらいのではないかなと思います。
下記はかなり前の動画なのですが(見たことある方多いはず)、コードの動きを可視化してくれていて、私はこれを見て、そうなんだ!となりました。「へー、setTimeoutはそっちにいってキューに入って、イベントループで引き上げられて処理されるのねー」って感じでした(見たまんまの感想)。記事を読んでてもよくわからなかった「キューに追加ってキューって何ぞや?」という謎がここで繋がった気がしました。
【参考 : Philip Roberts: Help, I’m stuck in an event-loop.】
https://www.youtube.com/watch?v=8aGhZQkoFbQ

下記の記事もわかりやすいです。シンプルに説明してくれているかつ図解がわかりやすいです。
【参考:JavaScript Visualized: Event Loop】
https://dev.to/lydiahallie/javascript-visualized-event-loop-3dif

さて、上記を理解した上で下記のコードはどの順番でコンソールに出力されるでしょうか。

これ、setTimeoutあるけど0秒だからすぐにコンソールに出力されるって思いませんか?
私は思いました。答えは下記。

2番が一番最後です。
あれ、0秒って待ちなしでコンソール出力されるんじゃないの?って思いませんか?(私は思いました)

でもこれイベントループの概念を理解してるとこの答えとてもしっくりきます。通常、関数はコールスタックにそのまま追加・管理・実行されますが、setTimeoutはwebAPIに渡され、タスクキューに追加、コールスタックが空になってやっと追加され実行される、という長旅をしてるんですよね。

つまり、setTimeoutの「0秒」の意味は、「0秒後に処理が実行される」という意味ではなく、「0秒後にタスクキューに追加される」ということを意味します。その後コールスタックの中にタスクが残っていたらそれらがなくなるまで待機しなければなりません。

では下記はどうでしょう。Promiseくんの登場です。

答えは下記。
いかがでしょうか。

setTimeoutを飛ばして、Promiseが先に処理されているのがわかります。
なぜこのようなことが起こったのでしょうか。

Microtasks(マイクロタスク)とMacroTasks(マクロタスク)

ここで理解が必要となるのは、Promiseはマイクロタスクに分類されるということです。マイクロタスクは簡単にいうと、一般のタスクよりも優先順位が高いタスクとなり、上で説明したタスクキューではなく、マイクロタスキューという別のキューに入ります。先ほどの理解に加えたいのは、コールスタックが空になった場合、イベントループはまずマイクロタスクキューにタスクがあるかを確認します。ある場合、マイクロタスクキューにあるタスクを全て実行します。その後、マイクロタスクキューが空になった場合、タスクキューにタスクがあるかを確認し、タスクがある場合は実行するという流れのようです。

マイクロタスクに対し、今までタスクキューと呼んでいたものはMacrotask queueと呼ばれ、そこで扱われるタスクをMacroTasks(マクロタスク)と呼ぶそう。名前がとても似ています。

マイクロタスクとマクロタスクの細かい処理のルールについてはまだ理解が浅いかなというのが正直なところなので今後の課題としたいです。

【参考:Tasks, microtasks, queues and schedules】
https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/

終わりに

非同期処理を理解するにあたって出会ったイベントループでしたが、この仕組みがとても面白いなって感じたし、可視化されるとこのように実行されているんだ!ととても興味を持ちました。今回はふんわり浅い知識をまとめるに止まりましたが、深掘りすると細かい知識がどんどん出てくるので引き続きこつこつ理解を深めていきたいです。

c.sakyou

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA