92
58

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

描いて理解するイベントループ

Posted at

はじめに

早速ですが Node.js で次のコードを実行したときの出力がどうなるのかわかるでしょうか?

setTimeoutに0秒を指定
console.log("start");

setTimeout(function() {
  console.log("Hello!!!");
}, 0); // 第2引数は 0 ミリ秒 = 0 秒待つ?

console.log("end");

実は、出力は以下の順になります。

実行結果
start
end
Hello!!!

「なんで?」と思った人向けに、順を追って解説します。
「あたりまえじゃないか」と思った人はブラウザバックしてしまいましょう。1

1. イベントループ

この記事では JavaScript の実行環境を「ワンオペで黙々と作業をこなす従業員」に例えて説明します。

まずは次の図を見てください。

EventLoop-001_outline.png

オフィスの片隅でベルトコンベアに乗って運ばれてくる資料を黙々と処理しつづけています。
作業内容は資料に書かれているので席を立つ必要もありません。

EventLoop-002_poll.png

もちろん休憩時間は与えられないので、ひとつのタスクが片付いたらすぐに次の作業に取り掛かります。

ポイント

ここではとりあえず、以下の点だけ認識しておいてください。

  • タスクは一方向に流れてくる
  • 一人きりで働いている2
  • 机が空くまで3次のタスクに着手できない

イベントループとは何か?

では、イベントループとはなんなのか?図に書いたとおり、JavaScript の処理というのは以下のステップを繰り返しています。この繰り返しのことを イベントループ と呼ぶのです。メインスレッドではひたすらループを回し、キューに溜まっているタスクを取り出しては実行していきます。

  1. タスクを処理する
  2. タスクが完了したら次のタスクを取り出す

2. イベントハンドリング

ブラウザでのイベントハンドリングを考えます。

ボタン <button id="btn"> をクリックしたときにログ出力する、というのは次のようにして実現できます。

イベントハンドラの登録
document.getElementById("btn").addEventListener("click", function ()   
  console.log("ボタンがクリックされました");
});

このコードが実行された時点では click イベントにコールバック関数が紐付けられただけです。ボタンをクリックすると下図のようにコンベア(キュー)の最後尾にその関数が追加されます。重要なのは、クリックしたときに関数が実行されるのではなく クリックしたときに関数をキューに追加する4 ということなのです。

EventLoop-003_push_callback.png

イベントハンドラはブロックされる

click イベントが発火した時点ではコールバック関数(イベントハンドラ)はキューに追加されるだけなので、それより前にあるタスクが片付かないと実行されません。

EventLoop-004_wait.png

このことを確認するためのサンプルコードを載せておきます。

デベロッパーツールでコンソールを見ながら操作してみるとわかりますが、イベントハンドラを登録しました と表示されてから 5 秒経つまでは、何度ボタンをクリックしても時刻は表示されません。そして、5秒経過したらボタンをクリックした回数分だけハンドラが実行され、ログ出力が行われます。

イベントハンドラがブロックされる例
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <script>
    window.onload = function () {
      document.getElementById("btn").addEventListener("click", function () {
        console.log(new Date().toISOString()); // 現在時刻を出力
      });
      console.log(`${new Date().toISOString()} : イベントハンドラを登録しました`);

      // 5 秒間ループ
      var start = Date.now();
      while (Date.now() - start < 5000) {
        // ループ中はボタンをクリックしてもログ出力されない
      }

      console.log(`${new Date().toISOString()} : タスク完了`);
    };
  </script>
</head>

<body>
  <button id="btn">クリックすると現在時刻がログ出力されます</button>
</body>

</html>

このことからわかるように、JavaScript ではひとつのタスクに時間をかけると他のすべてのタスクに影響が出てしまいます。ブラウザで動かす場合も、Node.js などでサーバーサイドで動かす場合も、イベントループは絶対に止めてはいけません。

3. setTimeout

さて次は setTimeout です。
これもさっきの click イベントのハンドラと似たことなのですが、時間に関係するイベントは管理方法が少し異なります。

EventLoop-005_time_event.png

setTimeout の第一引数で渡されたコールバック関数は、キューに追加されるのではなく実行予定の時刻をメモして箱にしまわれます。実はタスクが終了して次のループに入るとき、タスクを取り出す前に時刻がチェックされます。

EventLoop-006_timer_phase_1.png

時刻をチェックしたときに「すでに実行時刻を過ぎているもの」が存在すれば、そのタスクが実行されます。5

EventLoop-006_timer_phase_2.png

setTimeout は「○○秒後に関数を実行する」のではない

さきほどの click イベントの例と同じですが、イベントループが進まなければ時刻のチェックも行われません。たとえば次のコードを実行すると、1秒後に実行したい処理が5秒経たないと実行されません。処理が時間通りに行われなくなってしまうので、やはり何があってもループを止めてはならないのです。

ブロックされる例
console.log("start");
setTimeout(function() {
  console.log("1秒経ちました");
}, 1000); // 1 秒後に実行したい

// 5 秒間ループ
var start = Date.now();
while (Date.now() - start < 5000) {
}
console.log("5秒経ちました");

はじめの問題

ここで、はじめの問題に戻ってみましょう。

setTimeoutに0秒を指定
console.log("start");

setTimeout(function() {
  console.log("Hello!!!");
}, 0); // 第2引数は 0 ミリ秒 = 0 秒待つ?

console.log("end");
実行結果
start
end
Hello!!!

ここまで読んだ方なら、何故この順番に出力されるのか理解できるのではないかと思います。この場合の処理をイラストにしてみたので、ひとつずつ処理を確認してみてください。

EventLoop-007_setTimeout.png

おわりに

もともとは Node.js の勉強中に書いていた個人的なメモを書き直したものなので、他人が見て理解できる図になっているのか不安です。
それでも、自分と似た感性の人にとって理解の一助となれたら嬉しいです。

検証環境

この記事では、特に断りのない場合以下の環境で動作確認を行っています。

  • Node 15.0.1
  • Firefox 82.0

参考

  1. ずっと Java をやっていて JavaScript はほぼ初心者です。初心者なりの理解を書いた記事なので、誤りがあったらコメント欄でやさしく教えていただけると嬉しいです。

  2. シングルスレッドなので

  3. コールスタックが空になるまで。作業中はメモや資料などが積み重なってる(スタックしている)イメージなので机で例えています。

  4. キューで管理されるのは関数そのものなのか自信ありませんが、定性的な理解としては大きく間違っていないと思っています。

  5. 実行時間を過ぎていたらキューに追加する、の方が正しいかも。このあたりちゃんと理解できてないです

92
58
1

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
92
58

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?