LoginSignup
98
58

JavaScriptの非同期処理について

Last updated at Posted at 2023-12-05

JavaScriptの非同期処理に関しての理解

背景

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

いきなり非同期処理クイズです!

まず下記のソースコードの実行順を予測してみてください!
// 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");
🙈結果🙈
~ 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
当たった人、おめでとう🎉🎉🎉!!!今記事をクローズしても大丈夫です。もし当たったなかったら、これから一緒に謎を解けましょう!!!

クイズを解けるために、下記の知識が必要ですね

非同期処理、実際はインベントループでのタスクの処理順番を制御する仕組みです。

インベントループ

スクリーンショット 2023-12-06 11.43.26.png

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

※ こちら資料を参照している。

タスクキュー
  • タスクとしてのもの
    1、イベント(Event)
    2、パース(Parsing)
    3、コールバック(Callbacks)
    4、リソースの使用(Using a resource)
    5、DOM 操作への反応(Reacting to DOM manipulation)

    こちらの資料を参考している

  • タスクがタスクキューに追加タイミング
    1、新しい JavaScript のプログラムやサブプログラムが直接的に実行される時(コンソールからや、スクリプト要素内のコードを実行するなどの形式で)
    2、イベントが発火し、イベントのコールバック関数がタスクキューへと追加する時
    3、同一の供給源から来たタスクは同じタスクキューへと送られる
  • タスクキューで重要なルール
    1、複数あるタスクキューはどの順番に処理されるか決められていない
    2、同一のタスクキュー内に存在しているタスクは到着した順番に処理される
    3、タイムアウトやインターバルの時間が経過し、登録しておいたコールバックがタスクキューへと追加される時(例:setTimeout() や setInterval()など)
マイクロタスクキュー
  • マイクロタスクは、それを呼び出し関数やプログラムが実行された後にコールスタックが空になった後にのみ実行される短い関数です。

    • 例:Promiseのthen(), catch(), finally()メソッドなどの引数に渡すコールバック関数がマイクロタスク
  • マイクロタスクキューとタスクキューは異なるもの

  • マイクロタスクキューはタスクキューよりも優先的に処理される(単一タスク(Task)が実行された後にすべてのマイクロタスク(Microtask)を処理する)

  • コールスタックが空になったらマイクロタスクを処理される

    こちらの資料を参考している

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


// コンソールイベントはタスクとして処理される
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」
------------------------------
*/

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

本記事はこちらのブログを勉強して途中のまとめですが、これからも引き続き勉強して感想をまとめようと思っています。

FYI

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

98
58
0

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