15分で理解するJavaScriptのイベントループ

## はじめに
JavaScriptのイベントループについて、自分なりになるべくわかりやすく説明してみました。JavaScript初心者から中級者の方までが対象です。わかりにくいところや間違いがあった場合には、気軽にコメントで言ってもらえると嬉しいです！

JSConf EUでJavaScriptのイベントループについてとても分かりやすく説明されている動画があったので、それを元にまとめました。この記事を参考にイベントループを理解して、JavaScript上級者を目指しみましょう！

## JavaScriptとは
まず始めにJavaScriptがどういうプログラミング言語かを知るところから始めてみましょう。以下がJavaScriptの説明です。

`A single-threaded non-blocking asynchronous concurrent language. Has a call stack, an event loop, a callback queue some other apis and stuff`

ちょっと英語だと何言ってるかよくわからないですよね。なので、これを日本語に翻訳すると、

`JavaScriptとは、シングルスレッドでノンブロッキングかつ非同期処理、並行処理が可能な言語です。コールスタック、イベントループ、コールバックキューやその他諸々のAPIを保持しています`

翻訳しても、ちょっと何言ってるかわからないですよね。僕も動画を見終わるまで同じ状態でした。少しずつこの文章で出てくる用語を理解しつつ、JavaScriptが一体どういう言語なのかを一緒に理解していきましょうね。

## JavaScriptのruntime
まず、最初のステップとしてJavaScriptのruntime(runtime engine)がどのように構成されているかを理解しましょう。JavaScriptのruntime(例えばChrome内にあるV8)は、シンプルにすると以下のような構成になっています。大雑把に言うと、Heap Memoryはメモリのアロケーションを行い、Call Stackはスタックフレームなどの管理を行います。次にV8を理解するために、V8のソースコードを少しだけ見てみると、`setTimeout`や`DOM`や`HTTP request`の実装が、ここにはないことが分かります。では、これらの実装はどこにあるのでしょうか。

ブラウザが提供する機能としてWebAPIsというものがあります。それに`setTimeout`や`DOM`や`HTTP request`などの実装は含まれています。「本当にV8のソースコードにこれらの実装はないの？」って、気になる方は是非[V8のソースコード](https://github.com/v8/v8)を見て、確かめてみてください！
また、今回この記事では、[Heap Memory](https://developer.mozilla.org/ja/docs/Web/JavaScript/Memory_Management)の説明は行いませんので、ご了承ください。

![javascript_runtime.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633264/62277a97-9aee-0360-c491-f8e27ccfd02d.png)

## Call Stack
次のステップとして、Call Stackを理解しましょう。Call Stackとは大雑把にいうと現在実行中のプログラムのどこにいるかを記録しているデータ構造です。どの関数が現在実行されていて、その関数の中でどの関数が呼び出されたかを記録しています。関数を呼び出すときに、Call Stackにその関数をpush(追加)し、関数からreturnされるときや関数の最後まで実行が終わったときに、Call Stackから処理がpop(追い出される)されます。今回の記事はざっくりとした説明でイベントループを理解するのが趣旨なので、厳密なCall Stackの説明は省きますが、気になる方は[このサイト](https://developer.mozilla.org/ja/docs/Glossary/Call_stack)にて分かりやすく説明されているので、読んでみてください！

説明に戻ると、JavaScriptはシングルスレッドで動きます。1つのスレッドで動くのであれば、1つのCall Stackしか持ちません。つまりは、一回に一つのことしかできません。

以下のコードの実行ステップを考えながら、Call Stackがどのように変化していくか見てみましょう。main関数は今回、このプログラム自体を実行する関数を指すこととします。

```javascript

function multiply(a, b) {
  return a * b
}

function square(n) {
  return multiply(n, n);
}

function printSquare(n) {
  const squared = square(n);
  console.log(squared);
}

printSquare(4)
```

まずはじめに、`main`がCall Stackへpushされます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633264/42a501fc-c941-c97b-b2b2-3b79653b60a0.png" width=75% alt="">

そして、`printSquare(4)`がある行へ達します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633264/dae95c5c-89a2-26fa-fae7-7d686d74dabf.png" width=75% alt="">

次に`main`から`printSquare`が呼び出され、Call Stackに`printSquare(4)`がpushされます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633264/062ea246-f4be-c215-ffd4-34912adbae6d.png" width=75% alt="">

更にそこから`square`が呼び出され、Call Stackに`square(n)`がpushされます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633264/d00c04ed-2031-c9cb-1cdb-67d4154065e0.png" width=75% alt="">

次に`square`から`multiply`が呼び出され、Call Stackに`multiply(n, n)`がpushされます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633264/0fefc9b4-cc8d-0716-b0d4-903c705a1369.png" width=75% alt="">


そして`multiply(n, n)`の最後の行まで行きreturnまで辿り着くと、`multiply(n, n)`が、Call Stackからpopされます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633264/e17a760d-4499-db9e-03f6-372f45d108d0.png" width=75% alt="">

次に`multiply(n, n)`の実行結果を受けて、`square(n)`の最後の行まで行きreturnまで辿り着くと、`square(n)`が、Call Stackからpopされます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633264/9aaec6ec-c59c-7ba7-1e1a-b292168c07d1.png" width=75% alt="">


`square(n)`の実行結果を受けて、`printSquare(4)`が実行を続けると、`console.log(squared)`にぶち当たります。そこで、Call Stackに今度は`console.log(squared)`がpushされます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633264/0a3771e3-06ec-50a4-fb5f-6ab97ce5b578.png" width=75% alt="">

`console.log(squared)`が終わると、Call Stackからpopされます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633264/95e4226a-1cda-1af8-bef1-8f8fdc35df37.png" width=75% alt="">

`console.log(squared)`が実行し終わるとその先に、`printSquare`の処理は残っていないので、Call Stackから`printSquare`がpopされます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633264/239f5325-1600-b601-98b6-401d97e0121f.png" width=75% alt="">


そして、全ての実行が終わり、Call Stackが空になりコードが終了します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633264/22b48330-ad8e-a046-71f0-fba585c5c2c1.png" width=75% alt="">

### 例1
以下のコードでは、`baz()`をまず始めに呼び、そこから`bar()`が呼ばれ、更にそこから`foo()`が呼ばれ、最終的に`foo()`の中で新たに生成したエラーをthrowしています。

```javascript
function foo() {
  throw new Error('Oops!');
}

function bar() {
  foo();
}

function baz() {
  bar();
}

baz();
```

これをブラウザ上で動かしてみると、エラーが起きた時のCall Stackの状態を見ることができます。このエラーでは、`baz`から`bar`が呼び出され、`bar`から更に`foo`が呼び出され、そこでエラーが出たということが示されています。

```
Uncaught Error: Oops!
  at foo
  at bar
  at baz
```

### 例2
以下の例では、`foo`が関数の中で`foo`を呼びそれをreturnしています。

```javascript
function foo() {
  return foo();
}

foo();
```

実行するといつまで経っても、`foo()`の呼び出しがCall Stackからpopされないので、無限に`foo`がCall Stackにpushされ、スタックオーバーフローが起きてしまい、以下のようなエラーが出てしまいます。

```
Uncaught RangeError: Maximum call stack size exceeded
  at foo
  at foo
  at foo
  at foo
  at foo
  at foo
  at foo
  at foo
  at foo
  at foo
```

## Blocking
次のステップとしてblockingについて理解してみましょう。ある処理をしていることで、その処理が実行中に他の処理が実行できなくなってしまうような(例えば実行に時間がかかってしまい、後続の処理に進めなくなるような)処理のことをblockingであると言います(blockingであるかどうかの、厳密な定義は存在しません)。例えば、`console.log('hello')`を実行するのには大した時間は必要がないですが、while loopで100万回HTTPリクエストを送るようなコードであれば、時間はとてもかかってしまいます。while loopで100万回HTTPリクエストを送るようなコードは、blockingであると言えるでしょう。

以下の擬似コードでは、`$.getSync`により指定されたURLにHTTP requestを送ります。これらは一行一行、上から順番に実行されていくので、`foo.com`へリクエストを送り、そのリクエストに対してのレスポンスが返ってきて`foo`への代入が終わるまで、次の`bar`の代入へ行きません。これだとすべてが実行されるまでにとても時間がかかってしまいます。

```javascript
var foo = $.getSync('//foo.com');
var bar = $.getSync('//bar.com');
var qux = $.getSync('//qux.com');

console.log(foo)
console.log(bar)
console.log(qux)
```

上のコードのように同期的に上から順番に、ただただ処理を行っている事には問題があります。それは、このように実行結果を待つ(Call Stackの中身が一向に処理されない)ようなコードだと、ブラウザ自体の操作ができなくなってしまう(renderもできなければ、後続のコードを動かすこともできない)ことです。具体的に、それがどういうことなのかは、イベントループが深く関わってくる話なので、この記事の後半でイベントループと一緒に説明したいと思います。

## 並行性・イベントループ
冒頭でも言ったように、JavaScriptのruntimeは一回に一つのことしかできません。`setTimeout`もAJAXリクエストを送ることも何かを実行している(Call Stackが空ではない)ときには、実行できません。しかしブラウザはruntime以外にも機能を持っています。その機能の一つがWebAPIです。`setTimeout`はV8の中にあるわけではなく、WebAPIしてブラウザから提供されているものです。以下のコードでは、タイマーを5000msとして、`setTimeout`を呼んでいる例です。以下の例を用いてイベントループについて説明していきます。


```javascript
console.log('hi');

setTimeout(() => {
  console.log('there');
}, 5000);

console.log('Done')
```

まず、WebAPIは勝手にコードを書き換えることができません。したがって、Call Stackに実行結果を直接入れることはできません。そこで登場するのがイベントループです。イベントループの仕事はとても単純で、Call StackとTask Queueを監視し、もしCall Stackが空であれば、Task Queueの先頭の要素をCall Stackにpushします。上のプログラムを例に具体的に説明すると、まず最初にこのコードを実行すると、一番最初の例と同様に`main()`がCall Stackにpushされます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633264/e758db19-c739-aa71-3098-4c805e58786b.png" width=75% alt="">

そして、次に`console.log('hi')`がCall Stackへpushされます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633264/442bbe19-656a-8d58-3649-fb3f8970d365.png" width=75% alt="">

`console.log('hi')`を実行し終えると、次に`setTimeout`を実行する行へ到達します。今まで通りここでは、Call Stackへ`setTimeout`をpushします。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633264/8d02591c-a15e-0901-8891-3cf4794803b8.png" width=75% alt="">

`setTimeout`はWebAPIsが提供する機能なので、Call StackからWebAPIsに`setTimeout`を呼び出すようにいいます。すると、timerメソッドが動き、指定された時間、コールバック(`() => {console.log(there)}`)を受け取り、待ちます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633264/8c46276a-2402-adb6-083a-36d52528460c.png" width=75% alt="">

`setTimeout`はWebAPIsのtimerを呼ぶという役割を果たしたので、ここで、Call Stackからpopされます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633264/271907a5-0d91-e71a-14c8-27329ba621b5.png" width=75% alt="">

次の行の`console.log('Done')`をCall Stackに入れ、実行します。実行後にpopされます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633264/26e7d480-6b38-59cb-6837-511fcbad88df.png" width=75% alt="">

そして`main()`だけが残ります。WebAPIs側では、まだ5000msが経っていないので、それまで待機しています。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633264/6bdfd66c-5e3a-a2f6-f1ba-f62d4e97d4f5.png" width=75% alt="">

`main()`も最後にpopされ、この時点でCall Stackは空になります。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633264/6c126136-7b76-87ba-992f-b7691e1b4a49.png" width=75% alt="">

そして、timerが指定された時間が経つと、Task Queueにコールバック(`() => {console.log(there)}`)を渡します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633264/f9e00801-f96a-2dd4-054a-e026f4ae673a.png" width=75% alt="">

現在、Call Stackは空で、Task Queueの先頭には、コールバック(`() => {console.log(there)}`)があるので、それを今度はイベントループによって、Call Stackにpushします。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633264/85dc6fad-3df5-f0c9-5d81-9776c83bc438.png" width=75% alt="">

最後にコールバック(`() => {console.log(there)}`)の実行が終わり、Call Stackが空になり、すべてのコードの実行が終了します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633264/4638cac0-32f7-aaea-decd-a4bfe188a844.png" width=75% alt="">

## setTimeoutの注意点
先程説明したイベントループでは、Call Stackが空であるときにTask Queueから一つだけ処理を取り出し、Call Stackへpushして実行を行います。このことから分かるのは、`setTimeout`の第二引数で指定した時間が経っても、`setTimeout`の第一引数で指定した処理を実行できない場合があります。第二引数で指定した時間後に、確実に実行できるわけでないことを覚えておきましょう。最短だと指定した時間後に実行可能であるってことを是非覚えておいてください。

以下のプログラムを実行した場合、`console.log('there')`はすぐに実行されません。Call Stackが空になってから、`console.log('there')`は実行されます。今回のプログラムの場合であれば、一番最後に`console.log('there')`は実行されます。

```javascript
console.log('hi');

setTimeout(() => {
  console.log('there');
}, 0);

console.log('Done')
```

### 実行結果
```
hi
Done
there
```

### なぞなぞ
もうそろそろ読むのが嫌になってきているかもしれないので、なぞなぞを出します。以下のコードを実行すると、どのような実行結果になるか予想してみてください。[Loupe](http://latentflip.com/loupe/?code=JC5vbignYnV0dG9uJywgJ2NsaWNrJywgZnVuY3Rpb24gb25DbGljaygpIHsKICAgIHNldFRpbWVvdXQoZnVuY3Rpb24gdGltZXIoKSB7CiAgICAgICAgY29uc29sZS5sb2coJ1lvdSBjbGlja2VkIHRoZSBidXR0b24hJyk7ICAgIAogICAgfSwgMjAwMCk7Cn0pOwoKY29uc29sZS5sb2coIkhpISIpOwoKc2V0VGltZW91dChmdW5jdGlvbiB0aW1lb3V0KCkgewogICAgY29uc29sZS5sb2coIkNsaWNrIHRoZSBidXR0b24hIik7Cn0sIDUwMDApOwoKY29uc29sZS5sb2coIldlbGNvbWUgdG8gbG91cGUuIik7!!!PGJ1dHRvbj5DbGljayBtZSE8L2J1dHRvbj4%3D)というWebサイトでプログラムを実行すると、プログラムの実行中にどのようにTask QueueやCall Stackが変化していくかが見れるので、是非試してみてください！

```javascript
setTimeout(function timeout() {
    console.log('chrome')
}, 1000)

setTimeout(function timeout() {
    console.log('firefox')
}, 1000)

setTimeout(function timeout() {
    console.log('safari')
}, 1000)

setTimeout(function timeout() {
    console.log('opera')
}, 1000)
```

## Render Queue
Call Stackに処理がある場合には、ブラウザの画面にrender(ブラウザの画面上に描画すること)はできません。Call Stackの中の値が空になって初めて、renderingは行われます。例えば、750ms毎にrenderingを行うような処理の実行と同時に、以下のようなプログラムを実行すると、下に示すgifのような結果が得られます。Render QueueはCall Stackが空になっているときだけ、先頭から一つだけ処理を取り出し、renderingを行い、それ以外の場合はずっと待機しています。注意としては、Render Queueから取り出された処理は、Call Stackへはpushされないといことです。ここはTask Queueとは違うので覚えておきましょう。Task QueueはCall StackとRender Queueの両方が空になっているときだけ、処理をCall Stackにpushします。実際にこのプログラムを実行したときに、どのような処理になるかは[loupe](http://latentflip.com/loupe/?code=JC5vbignYnV0dG9uJywgJ2NsaWNrJywgZnVuY3Rpb24gb25DbGljaygpIHsKICAgIHNldFRpbWVvdXQoZnVuY3Rpb24gdGltZXIoKSB7CiAgICAgICAgY29uc29sZS5sb2coJ1lvdSBjbGlja2VkIHRoZSBidXR0b24hJyk7ICAgIAogICAgfSwgMjAwMCk7Cn0pOwoKY29uc29sZS5sb2coIkhpISIpOwoKc2V0VGltZW91dChmdW5jdGlvbiB0aW1lb3V0KCkgewogICAgY29uc29sZS5sb2coIkNsaWNrIHRoZSBidXR0b24hIik7Cn0sIDUwMDApOwoKY29uc29sZS5sb2coIldlbGNvbWUgdG8gbG91cGUuIik7!!!PGJ1dHRvbj5DbGljayBtZSE8L2J1dHRvbj4%3D)を動かしてみてみると、とてもわかりやすいです。

```javascript
$.on('button', 'click', function onClick() {
    setTimeout(function timer() {
        console.log('You clicked the button!');    
    }, 2000);
});

console.log("Hi!");

setTimeout(function timeout() {
    console.log("Click the button!");
}, 5000);

console.log("Welcome to loupe.");
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633264/dfdb2a18-2b65-5d6a-1381-638fdc7a8cfe.gif" width="120%" alt="event loop">

## まとめ
### イベントループ
JavaScriptはシングルスレッドで動き、一つのCall Stackを持ちます。Call Stackだけだと、重たい処理があったときにブラウザの表示や後続の処理をblockしてしまいます。これを解決するためにイベントループという仕組みがあります。イベントループによって、Call Stack、WebAPI、Task Queue、Render Queueを用いて、非同期的に並行で処理を行えます。

### 優先順位
`Call Stack > Render Queue > Task Queue` の順番で優先されると覚えておいてください。また、以下のことを覚えておきましょう。

- Call Stackが空であれば、Render Queueから処理が一つ取り出されrenderingが行われる
- Call StackとRender Queueが空であれば、Task Queueの先頭から処理が一つだけ取り出され、Call Stackへpushされる

## おわりに
英語 Version
`A single-threaded non-blocking asynchronous concurrent language. Has a call stack, an event loop, a callback queue some other apis and stuff`

日本語 Version
`JavaScriptとは、シングルスレッドでノンブロッキングかつ非同期処理、並行処理が可能な言語です。コールスタック、イベントループ、コールバックキューやその他諸々のAPIを保持しています`

冒頭にあった、JavaScriptの説明を理解できましたか? 是非コメントやフィードバック、LGTMをお願いします！これからの記事作成のモチベーションに繋がります！

## 参考・引用
- [イベントループとは一体何ですか？|Philip Roberts|JSConf EU](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=359s)
- [loupe](http://latentflip.com/loupe/?code=JC5vbignYnV0dG9uJywgJ2NsaWNrJywgZnVuY3Rpb24gb25DbGljaygpIHsKICAgIHNldFRpbWVvdXQoZnVuY3Rpb24gdGltZXIoKSB7CiAgICAgICAgY29uc29sZS5sb2coJ1lvdSBjbGlja2VkIHRoZSBidXR0b24hJyk7ICAgIAogICAgfSwgMjAwMCk7Cn0pOwoKY29uc29sZS5sb2coIkhpISIpOwoKc2V0VGltZW91dChmdW5jdGlvbiB0aW1lb3V0KCkgewogICAgY29uc29sZS5sb2coIkNsaWNrIHRoZSBidXR0b24hIik7Cn0sIDUwMDApOwoKY29uc29sZS5sb2coIldlbGNvbWUgdG8gbG91cGUuIik7!!!PGJ1dHRvbj5DbGljayBtZSE8L2J1dHRvbj4%3D)
- [V8 JavaScript Engine](https://github.com/v8/v8)
- [コールスタック](https://developer.mozilla.org/ja/docs/Glossary/Call_stack)
- [メモリ管理](https://developer.mozilla.org/ja/docs/Web/JavaScript/Memory_Management)