JavaScriptの非同期処理について

JavaScriptの非同期処理に関しての理解
# 背景
フロント開発で、よくPromise、await、then、catch、finally、asyncなどのキーワードがありますね、これらと一緒に出る単語は”同期処理”、”非同期処理”ですね。
多分Javaでの逐次処理、並列処理、並行処理と類似しているものと最初勝手にそう思いました。
実際にフロントエンドの開発を始め、この非同期処理の理解についてすごく苦労してました。（ただのプロセス上コードを一行ずつに実行するか、プロセスが複数に同時に処理することではなかったですね😭）


# いきなり非同期処理クイズです！
###### まず下記のソースコードの実行順を予測してみてください！

~~~javascript
// timeandpromise.js

console.log("[A] 🦖 MAINLINE: Start");

setTimeout(() => {
  console.log("[B] ⏰ TIMERS: setTimeout[0ms]");

  Promise.resolve("1st Promise")
    .then((value) => console.log("[C] 👦 MICRO: Resolved value:", value))
    .then(() => console.log("[D] 👦 MICRO: Next chain"));

  setTimeout(() => {
    console.log("[E] ⏰ TIMERS: setTimeout[0ms]");

    Promise.resolve("2nd Promise")
      .then((value) => console.log("[F] 👦 MICRO: Resolved value:", value))
      .then(() => console.log("[H] 👦 MICRO: Next chain"));
  });
});

setTimeout(() => {
  console.log("[I] ⏰ TIMERS: setTimeout[0ms]");

  Promise.resolve("3rd Promise")
    .then((value) => console.log("[J] 👦 MICRO: Resolved value:", value))
    .then(() => console.log("[K] 👦 MICRO: Next chain"));
});

Promise.resolve().then(() => console.log("[L] 👦 MICRO: then"));

console.log("[M] 🦖 MAINLINE: End");
~~~


<details><summary>🙈結果🙈</summary>

```linux
~ deno run timeandpromise.js                                          
[A] 🦖 MAINLINE: Start
[M] 🦖 MAINLINE: End
[L] 👦 MICRO: then
[B] ⏰ TIMERS: setTimeout[0ms]
[C] 👦 MICRO: Resolved value: 1st Promise
[D] 👦 MICRO: Next chain
[I] ⏰ TIMERS: setTimeout[0ms]
[J] 👦 MICRO: Resolved value: 3rd Promise
[K] 👦 MICRO: Next chain
[E] ⏰ TIMERS: setTimeout[0ms]
[F] 👦 MICRO: Resolved value: 2nd Promise
[H] 👦 MICRO: Next chain
```
</details>



###### 当たった人、おめでとう🎉🎉🎉！！！今記事をクローズしても大丈夫です。もし当たったなかったら、これから一緒に謎を解けましょう！！！

# クイズを解けるために、下記の知識が必要ですね
<font color = 'green'>非同期処理、実際はインベントループでのタスクの処理順番を制御する仕組みです。</font>
###### インベントループ
![スクリーンショット 2023-12-06 11.43.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2987895/d8bbdc6a-23e1-0fe1-6cfa-63654c32e98d.png)

- イベントロープで複数のタスクキューがある
- イベントループで通常一つのマイクロタスクキューがある

※ こちら資料を参照している。 
- https://v8.dev/blog/fast-async#tasks-vs.-microtasks
- https://html.spec.whatwg.org/multipage/webappapis.html#event-loops
- https://www.youtube.com/watch?v=u1kqx6AenYw

###### タスクキュー
- 
  <dl>
  <dt>タスクとしてのもの</dt>
  <dd>１、イベント(Event)</dd>
  <dd>２、パース(Parsing)</dd>
  <dd>３、コールバック(Callbacks)</dd>
  <dd>４、リソースの使用(Using a resource)</dd>
  <dd>５、DOM 操作への反応(Reacting to DOM manipulation)</dd>
  <dl>
  
  [こちらの資料を参考している](https://html.spec.whatwg.org/multipage/webappapis.html#event-loops)

- 
  <dl>
  <dt>タスクがタスクキューに追加タイミング</dt>
  <dd>１、新しい JavaScript のプログラムやサブプログラムが直接的に実行される時(コンソールからや、スクリプト要素内のコードを実行するなどの形式で)</dd>
  <dd>２、イベントが発火し、イベントのコールバック関数がタスクキューへと追加する時</dd>
  <dd>３、同一の供給源から来たタスクは同じタスクキューへと送られる</dd>
  <dl>

- <dl>
  <dt>タスクキューで重要なルール</dt>
  <dd>１、複数あるタスクキューはどの順番に処理されるか決められていない</dd>
  <dd>２、同一のタスクキュー内に存在しているタスクは到着した順番に処理される</dd>
  <dd>３、タイムアウトやインターバルの時間が経過し、登録しておいたコールバックがタスクキューへと追加される時(例：setTimeout() や setInterval()など)</dd>
  <dl>

###### マイクロタスクキュー
- マイクロタスクは、それを呼び出し関数やプログラムが実行された後にコールスタックが空になった後にのみ実行される短い関数です。
  - 例：Promiseのthen(), catch(), finally()メソッドなどの引数に渡すコールバック関数がマイクロタスク
- マイクロタスクキューとタスクキューは異なるもの
- マイクロタスクキューはタスクキューよりも優先的に処理される（<font color = 'red'>単一タスク(Task)が実行された後にすべてのマイクロタスク(Microtask)を処理する）</font>
- コールスタックが空になったらマイクロタスクを処理される
  
  [こちらの資料を参考している](https://zenn.dev/estra/books/js-async-promise-chain-event-loop/viewer/d-epasync-task-microtask-queues#%E3%83%9E%E3%82%A4%E3%82%AF%E3%83%AD%E3%82%BF%E3%82%B9%E3%82%AF%E3%82%AD%E3%83%A5%E3%83%BC)

# 上記のヒントを持って、もう一度クイズを分析しよう！
~~~javascript

// コンソールイベントはタスクとして処理される
console.log("[A] 🦖 MAINLINE: Start");

setTimeout(() => {
  // コールバックはタスクとして処理される
  console.log("[B] ⏰ TIMERS: setTimeout[0ms]");

  Promise.resolve("1st Promise")
   // コールバックはマイクロタスクとして処理される
    .then((value) => console.log("[C] 👦 MICRO: Resolved value:", value))
    .then(() => console.log("[D] 👦 MICRO: Next chain"));

  setTimeout(() => {
      // コールバックはタスクとして処理される
    console.log("[E] ⏰ TIMERS: setTimeout[0ms]");

    Promise.resolve("2nd Promise")
       // コールバックはマイクロタスクとして処理される
      .then((value) => console.log("[F] 👦 MICRO: Resolved value:", value))
      .then(() => console.log("[H] 👦 MICRO: Next chain"));
  });
});

setTimeout(() => {
  // コールバックはタスクとして処理される
  console.log("[I] ⏰ TIMERS: setTimeout[0ms]");

  Promise.resolve("3rd Promise")
     // コールバックはマイクロタスクとして処理される
    .then((value) => console.log("[J] 👦 MICRO: Resolved value:", value))
    .then(() => console.log("[K] 👦 MICRO: Next chain"));
});

// コールバックはマイクロタスクとして処理される
Promise.resolve().then(() => console.log("[L] 👦 MICRO: then"));

// コンソールイベントはタスクとして処理される
console.log("[M] 🦖 MAINLINE: End");

/* 
-----------Round1-------------
タスク「コンソールA」が処理、→ A
タイマのタスク「コンソールB及び中のすべてタスク」がタスクキューに追加される、
タイマのタスク「コンソールI及び中のすべてタスク」がタスクキューに追加される、
Promiseのマイクロタスク「コンソールL」がマイクロタスクキューに追加される、
タスク「コンスールM」が処理　→ M
現時点
　　　タスクキュー：「コンソールB及び中のすべてタスク」、「コンソールI及び中のすべてタスク」
　　　マイクロタスクキュー：「コンソールL」
-----------Round2-------------
単一タスクが終わったら、すべてのマイクロタスクを処理する
マイクロタスクキューでの「コンソールL」を処理　→　L
現時点
　　　タスクキュー：「コンソールB及び中のすべてタスク」、「コンソールI及び中のすべてタスク」
　　　マイクロタスクキュー：
-----------Round3-------------
タイマになったら、タスクキューでのタイマタスクを処理する（FIFO）
「コンソールB及び中のすべてタスク」
タスク「コンソールB」を処理、
Promiseのマイクロタスク「コンソールC」、「コンソールD」が順番にマイクロタスクキューに追加される、
タイマのタスク「コンソールE及び中のすべてタスク」がタスクキューに追加される、
現時点
　　　タスクキュー：「コンソールI及び中のすべてタスク」、「コンソールE及び中のすべてタスク」
　　　マイクロタスクキュー：「コンソールC」、「コンソールD」
-----------Round4-------------
単一タスクが終わったら、すべてのマイクロタスクを処理する
マイクロタスクキューでマイクロタスクを処理する（FIFO）
「コンソールC」を処理　→ C
「コンソールD」を処理　→ D
現時点
　　　タスクキュー：「コンソールI及び中のすべてタスク」、「コンソールE及び中のすべてタスク」
　　　マイクロタスクキュー：
-----------Round5-------------
タイマになったら、タスクキューでのタイマタスクを処理する（FIFO）
「コンソールI及び中のすべてタスク」
タスク「コンソールI」を処理、
Promiseのマイクロタスク「コンソールJ」、「コンソールK」が順番にマイクロタスクキューに追加される
現時点
　　　タスクキュー：「コンソールE及び中のすべてタスク」
　　　マイクロタスクキュー：「コンソールJ」、「コンソールK」
-----------Round6-------------
単一タスクが終わったら、すべてのマイクロタスクを処理する
マイクロタスクキューでマイクロタスクを処理する（FIFO）
「コンソールJ」を処理　→ J
「コンソールK」を処理　→ K
現時点
　　　タスクキュー：「コンソールE及び中のすべてタスク」
　　　マイクロタスクキュー：
-----------Round7-------------
タイマになったら、タスクキューでのタイマタスクを処理する（FIFO）
「コンソールE及び中のすべてタスク」
タスク「コンソールE」を処理、
Promiseのマイクロタスク「コンソールF」、「コンソールH」が順番にマイクロタスクキューに追加される
現時点
　　　タスクキュー：
　　　マイクロタスクキュー：「コンソールF」、「コンソールH」
-----------Round8-------------
単一タスクが終わったら、すべてのマイクロタスクを処理する
マイクロタスクキューでマイクロタスクを処理する（FIFO）
「コンソールF」を処理　→ F
「コンソールH」を処理　→ H
現時点
　　　タスクキュー：
　　　マイクロタスクキュー：「コンソールF」、「コンソールH」
------------------------------
*/
~~~

非同期処理を全部理解するまでは、まだまだ色々なヒントを揃わないといけないですが、もし少しても非同期処理に興味が有れば嬉しいです。

本記事は[こちらのブログ](https://zenn.dev/estra/books/js-async-promise-chain-event-loop)を勉強して途中のまとめですが、これからも引き続き勉強して感想をまとめようと思っています。

## FYI
明日は、@zushi_ryotaさんが、Mockito-Kotlinを使ってテストコードを書いてみる！ぜひ、お楽しみください〜