JavaScriptにおける非同期処理についての備忘録

# はじめに
JavaScriptの非同期処理について、詳しく調べる機会があったので備忘録をまとめたいと思います。
# 1. 同期処理
まず初めに、JavaScriptの同期処理について説明します。

JavaScriptは、基本的にシングルスレッドで動作するため、ソースの上から下へ順に処理が実行されます。
例えば以下の実装の場合、printLogメソッドの中で標準出力していますが、結果はconsole.logの呼び出し順に出力されます。
```javascript:JavaScript
function printLog() {
  console.log("1: 同期処理開始");
  console.log("2: 同期処理終了");
}

// 関数を実行
printLog();
```

```:実行結果
1: 同期処理開始
2: 同期処理終了
```
単純にみえるこの処理ですが、実は裏ではJavaScriptエンジンが以下の流れで処理行っています。

1. JavaScriptエンジンがコードをパース・解釈し、インタープリンターが`printLog()`をコールスタックに積んで実行
```plantuml
skinparam dpi 100
skinparam Padding 5

    stack "           " as leftSpacer #white;line:white{
    queue "aaa               " #white;line:white;text:white
    }
    stack stackVeryLOOOOOOOOOOOOOOOOOOOg as "CallStack" #aliceblue;line:blue;text:blue {
        queue "①printLog()                             "
    }
    stack "           " as rightSpacer #white;line:white{
    queue "aab               " #white;line:white;text:white
    }

```

2\. `console.log("1:同期処理開始")`が`printLog()`内で呼ばれる。
　→インタープリンターがコールスタックに積んで実行
```plantuml
skinparam dpi 100
skinparam Padding 5

    stack "           " as leftSpacer #white;line:white{
    queue "aaa               " #white;line:white;text:white
    }
    stack stackVeryLOOOOOOOOOOOOOOOOOOOg as "CallStack" #aliceblue;line:blue;text:blue {
        queue ku[
        ①printLog()

        ②console.log("1:同期処理開始")
        ]
    }
    stack "           " as rightSpacer #white;line:white{
    queue "aab               " #white;line:white;text:white
    }

```

3\. 2.でコールスタックに積んだ`console.log("1:同期処理開始")`が終了後、コールスタックから削除
```plantuml
skinparam dpi 100
skinparam Padding 5

    stack "           " as leftSpacer #white;line:white{
    queue "aaa               " #white;line:white;text:white
    }
    stack stackVeryLOOOOOOOOOOOOOOOOOOOg as "CallStack" #aliceblue;line:blue;text:blue {
        queue "①printLog()                             "
    }
    stack "           " as rightSpacer #white;line:white{
    queue "aab               " #white;line:white;text:white
    }

```

4\. `console.log("2:同期処理終了")`が`printLog()`内で呼ばれる。
　→ インタープリンターがコールスタックに積んで実行
```plantuml
skinparam dpi 100
skinparam Padding 5

    stack "           " as leftSpacer #white;line:white{
    queue "aaa               " #white;line:white;text:white
    }
    stack stackVeryLOOOOOOOOOOOOOOOOOOOg as "CallStack" #aliceblue;line:blue;text:blue {
        queue ku[
        ①printLog()

        ②console.log("2:同期処理開始")
        ]
    }
    stack "           " as rightSpacer #white;line:white{
    queue "aab               " #white;line:white;text:white
    }

```

5\. 4.でコールスタックに積んだ`console.log("2:同期処理終了")`が終了後、コールスタックから削除
```plantuml
skinparam dpi 100
skinparam Padding 5

    stack "           " as leftSpacer #white;line:white{
    queue "aaa               " #white;line:white;text:white
    }
    stack stackVeryLOOOOOOOOOOOOOOOOOOOg as "CallStack" #aliceblue;line:blue;text:blue {
        queue "①printLog()                             "
    }
    stack "           " as rightSpacer #white;line:white{
    queue "aab               " #white;line:white;text:white
    }

```

6\. 1.でコールスタックに積んだ`printLog()`が終了後、コールスタックから削除
```plantuml
skinparam dpi 100
skinparam Padding 5

    stack "           " as leftSpacer #white;line:white{
    queue "aaa               " #white;line:white;text:white
    }
    stack stackVeryLOOOOOOOOOOOOOOOOOOOg as "CallStack" #aliceblue;line:blue;text:blue {
        queue "takahashi                             " #aliceblue;line:aliceblue;text:aliceblue
    }
    stack "           " as rightSpacer #white;line:white{
    queue "aab               " #white;line:white;text:white
    }

```


このように、JavaScriptではソースが上から順に同期的に処理されます。

### (補足)JavaScriptエンジンの主要なコンポーネント
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1618611/d41ac105-dc37-8863-db32-c26f40315712.png)

# 2. 非同期処理
さて、ここからが本題です。
前項で記載した通り、JavaScriptは基本的にシングルスレッドであるため、処理が並列して実行されることはありません。
例えば、ネットワーク通信やファイル読み込みのような時間がかかる処理であっても、処理を同期的に行えば(非同期処理を使用しなければ)、前段の処理が完了するまで他の処理は止まってしまいます。
以下に例を示します。

 #### 例：非同期処理を使わないとどうなるか？
```javascript:JavaScript
console.log("Start");

function waitSync() {
  let start = Date.now();
  while (Date.now() - start < 3000) {
    // 3秒間何もしない（ブロッキング）
  }
  console.log("Sync Done");
}

waitSync();

console.log("End");
```
```
###### 結果 ######

Start
（3秒間フリーズ）
Sync Done
End
```

このようにJavaScriptはシングルスレッドであるため、実行時間の長い処理を呼び出すと、その処理の完了まで他の処理を動かすことができません。
この問題を解決してくれるのが非同期処理です。
#### 非同期処理とは
> 長く続く可能性のあるタスクを開始しても、そのタスクが完了するまで待つのではなく、そのタスクの実行中も他のイベントに応答できるようにする技術です。
[(引用元)非同期 JavaScript 入門](https://developer.mozilla.org/ja/docs/Learn_web_development/Extensions/Async_JS/Introducing)
#### 例：非同期処理を使うとどうなる？
```javascript:JavaScript
console.log("Start");

setTimeout(() => {
  console.log("Async Done");
}, 3000);

console.log("End");
```
```
###### 結果 ######

Start
End
（3秒後に）Async Done
```
上記のように非同期処理(ここでは`setTimeout()`)を使うと、3秒間待つ間に `console.log("End")` が先に実行され、その後、コールバックに指定した`console.log("Async Done")`が実行されます。
***→時間のかかる処理の終了を待たずに、他の処理を行うことができる！***

# 3. 非同期処理の仕組み
非同期処理については前項で解説した通りです。
前項までを調べたうえで、小生はふと疑問に思いました。

***JavaScriptはシングルスレッドなのに、非同期処理使ってる時って並列で処理が動いてない？***

ようは前項の例でいくと、```setTimeout()```が流れている裏で、```console.log("End");```が動いてるやん！
話違うやん！となったわけです。

この疑問についても解消したので、メモしておこうかと思います。


#### 図解：ブラウザの全体像
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1618611/4f0e340f-c7ec-165d-1518-e56101fd9300.png)
[(引用元)](https://meetup-jp.nhncloud.com/896)

唐突に図を張りましたが、ブラウザのコンポーネントにはJavascriptエンジンのほかに、以下のものがあります。

#### Web APIsとは
> Web APIとは、Webブラウザが提供する機能をJavaScriptから利用できるようにするインターフェースのことです。​これにより、JavaScriptはブラウザ内でさまざまな操作を実行できます。
#### タスクキューとは
> タスクキュー（Task Queue） は、JavaScriptの非同期処理（setTimeout や fetch のコールバックなど）が完了したときに、実行待ちの処理を一時的に保持するキュー（待ち行列） です。
#### イベントループ
> Web APIとは、Webブラウザが提供する機能をJavaScriptから利用できるようにするインターフェースのことです。​これにより、JavaScriptはブラウザ内でさまざまな操作を実行できます。

これらのブラウザのコンポーネントがうまいこと処理してくれているわけですが、どのように処理を行っているか例を使って詳細に見ていきます。
```javascript:JavaScript
console.log("Start");

setTimeout(() => {
  console.log("Async Done");
}, 3000);

console.log("End");
```
1. JavaScriptエンジンがコードをパース・解釈し、インタープリンターが`console.log("Start")`をコールスタックに積んで実行
※実行後コールスタックから削除

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1618611/88eb0012-2fdc-4787-a926-5aca0b2707cb.png)

2\. `setTimeout()`がコールスタックに積まれ、Web API にタイマー開始を要請
※要請が済めばコールスタックからは削除
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1618611/3cba4df4-5466-44b2-a8e5-6485b4c068d4.png)

3-1\. 2.で実行した`setTimeout()`が終了後※3秒経過後、コールバックに設定した内容`console.log("Async Done");`をタスクキューに設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1618611/ad8c6e6b-2a18-4f6d-bd54-e87e1bde812f.png)

3-2\. 2.でWeb APIに`setTimeout()`を任せている間、コールスタックが空くので、
次の処理`console.log("End");`を実行
※実行後コールスタックから削除
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1618611/44ec7abd-a594-4a55-aafd-bbed59c19a2c.png)



4\. `console.log("End");`の実行後、コールスタックが空くかつ、プログラムのトップダウンに他の関数も残っていないので、タスクキューにためておいた`console.log("Async Done");`を実行する。
→タスクキューに空きがあるかを常にイベントループが監視していて、空きができ次第、タスクキューにためておいた処理を実行する！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1618611/af9b1134-632c-4091-bd56-64bf317f21c1.png)

# まとめ
今回は、JvaScriptの非同期処理について、まとめてみました。
調べてみると中々奥が深いですな。


# 参考にした、参考になるサイト
[JavaScriptとイベントループ](https://meetup-jp.nhncloud.com/896)
[JavaScript イベントループの仕組みをGIFアニメで分かりやすく解説](https://coliss.com/articles/build-websites/operation/javascript/javascript-visualized-event-loop.html)
[JavaScriptのイベントループまわりは、どういう仕組みで動いているのか？](https://zenn.dev/rinda_1994/articles/fb5304da03d5d7#%E5%8F%82%E8%80%83%E8%A8%98%E4%BA%8B)
[JavaScriptにおける非同期処理とは](https://qiita.com/Hirohana/items/6a7fd4c7f4c9ccb1f6da)