Node.jsをアニメーション化: イベントループ
私たちはみんな、JavaScriptとNode.jsがシングルスレッドだって聞いたことがあるけど、実際にはどういう意味なの?
それは、JavaScriptは一度に一つのことしかできないということだよ。例えば、同時に数値の乗算と加算をすることはできないよね。普通は操作を順番に行うものだ。加算してから乗算をするとかその逆をするとかね。現代のコンピュータは速いから、2つ以上の連続したタスクが同時に計算されているように見えるけど、例外もあるよ。
私たちは皆、あの遅いウェブサイトからデータを収集しようとしたり、データベースのクエリーの結果を30秒以上待ったことがあるよね。遅いデータベースのクエリーが原因で、他のタスクの実行をブロックさせてしまいたいと思うかい?幸いなことに、Node.jsはLibuvのおかげで、そんなことにはならないんだ。Libuvとは、イベントループやネットワークリクエスト、DNSの解決、ファイルシステムの操作、データ暗号化などのタスクを非同期で処理するC++ライブラリのことさ。
Node.jsがデータベースクエリーなどのタスクを行う際に内部でどうなってるか、このコードのステップを追って探求してみよう。
V8 JavaScriptエンジンは、プログラムのどの部分が実行中かを追跡するためのコールスタックという重要な部品を管理しているよ。JavaScript関数を呼び出すたびに、それはコールスタックにプッシュされるんだ。関数が終わりに達するか、return
ステートメントに到達すると、スタックからポップされるんだよ。
例えば、console.log('Starting Node.js')
という行のコードがコールスタックに追加されて、コンソールにStarting Node.js
を表示するんだ。そうすることで、log
関数の終わりに達し、コールスタックから取り除かれるんだ。
次の行はデータベースのクエリーだ。これらのタスクはすぐにポップされるんだ。何故かって?長い時間がかかるかもしれないからね。これらはLibuvに渡されて、Node.jsが他のコードを実行する間、Libuvがバックグラウンドで非同期に処理するんだ。
将来的に、Node.jsはクエリーをどう扱うかを知るでしょう。なぜなら、タスクの結果やエラーを処理するための指示と関連するコールバック関数を関連付けているからね。私たちのケースでは、シンプルなconsole.log
だけど、生産アプリケーションでは複雑なビジネスロジックやデータ処理をすることもあるんだ。
Libuvがクエリーをバックグラウンドで処理している間、私たちのJavaScriptはブロックされずに、console.log('Before query result')
を実行する続行できるんだ。
クエリーが終わると、そのコールバックは実行されるためにすぐにI/Oイベントキューに押し込まれるんだ。*.*イベントループはキューとコールスタックを繋ぐ働をするんだ。コールスタックが空かどうかを確認し、最初のキューアイテムを実行移動させるんだ。
コードはこちらで利用可能だよ https://github.com/fabrilallo/event-loop-1
イベントループについてのクイズ
下記のコードがコンソールに何を出力するか考えてみよう。
結論
イベントループ、委譲、そして非同期処理のメカニズムは、Node.jsが何千もの接続を処理し、大きなファイルの読み書きやタイマーの処理をしながら他のコード部分で作業を続けるための秘密の材料だよ。
この記事では、Libuvの重要な役割と、たくさんの長時間実行されるタスクをどのように処理するかについて見たね。同時に、イベントループを通じて、非同期操作のコールバックとI/Oイベントキュー、コールスタックとの間の架け橋/コネクターとしての役割を見てみたんだ。次の記事では、イベントループの異なるフェーズにおいてタイマー、I/O、プロミス、そしてティックがどう処理されるかをさらに詳しく探っていくよ。
記事が気に入ったら、Twitterでフォローしてね @fabriziolallo と @andrewhu368
こちらの記事はdev.toの良い記事を日本人向けに翻訳しています。
https://dev.to/nodedoctors/an-animated-guide-to-nodejs-event-loop-3g62