ホームページ  >  記事  >  ウェブフロントエンド  >  ノードイベントループ内のマイクロタスクキューの詳細な分析

ノードイベントループ内のマイクロタスクキューの詳細な分析

青灯夜游
青灯夜游転載
2023-04-13 17:39:151834ブラウズ

ノードイベントループ内のマイクロタスクキューの詳細な分析

前の記事では、イベント ループが Node.js の重要な部分であり、同期コードと非同期コードの実行を調整するために使用されることを学びました。

これは 6 つの異なるキューで構成されます。 nextTick キューと Promise キュー (マイクロタスク キューと呼ばれる)、タイマー キュー、I/O キュー、チェック キュー、そして最後にシャットダウン キューです。 [関連チュートリアルの推奨事項: nodejs ビデオ チュートリアル プログラミング指導 ]

各ループで、コールバック関数は適切なタイミングでデキューされ (デキューされ)、実行されます。スタック上にあります。この記事から始めて、イベント ループの理解が正しいことを確認するためにいくつかの実験を実行します。

最初の実験セットでは、nextTick キューと Promise キューに焦点を当てます。ただし、実験に入る前に、コールバック関数をキューに入れる方法を理解する必要があります。

注: すべての実験は CommonJS モジュール形式を使用して実施されました。

エンキュー コールバック関数

コールバック関数を nextTick キューに追加するには、組み込みの process.nextTick() メソッドを使用できます。構文は非常に単純です: process.nextTick(callbackFn)。メソッドがコール スタックで実行されると、コールバック関数が nextTick キューに追加されます。

コールバック関数を Promise キューに追加するには、Promise.resolve().then(callbackFn) を使用する必要があります。 Promise が解決されると、then() に渡されたコールバック関数が Promise キューにエンキューされます。

コールバック関数を 2 つのキューに追加する方法がわかったので、最初の実験を開始しましょう。

実験 1

コード

// index.js
console.log("console.log 1");
process.nextTick(() => console.log("this is process.nextTick 1"));
console.log("console.log 2");

このコードは 3 つの異なるステートメントを記録します。 2 番目のステートメントは、process.nextTick() を使用して、コールバック関数を nextTick キューに入れます。

視覚化

ノードイベントループ内のマイクロタスクキューの詳細な分析

最初の console.log() ステートメントは、実行のためにコール スタックにプッシュされます。適切なメッセージをコンソールに記録し、スタックをポップします。

次に、コール スタックで process.nextTick() を実行し、コールバック関数を nextTick キューにエンキューして、ポップアップします。この時点では実行する必要があるユーザー作成のコードがまだあるため、コールバック関数は順番を待つ必要があります。

実行は続行され、最後の console.log() ステートメントがスタックにプッシュされ、メッセージがコンソールに記録され、関数がコール スタックからポップされます。これで、実行するユーザー作成の同期コードがなくなったので、制御はイベント ループに入ります。

nextTick キューのコールバック関数がコール スタックにプッシュされ、console.log() がコール スタックにプッシュされて実行され、対応するメッセージがコンソールに記録されます。

console.log 1
console.log 2
this is process.nextTick 1

当然の結果

すべてのユーザー作成の同期 JavaScript コードは、非同期コードより前に実行されます。

2 番目の実験に進みましょう。

実験 2

コード

// index.js
Promise.resolve().then(() => console.log("this is Promise.resolve 1"));
process.nextTick(() => console.log("this is process.nextTick 1"));

Promise.resolve().then() 呼び出しと process.nextTick() 呼び出しがあります。

視覚化

assets_YJIGb4i01jvw0SRdL5Bt_b32c606155c14ecfa60c8dc102f3bcf0_compressed (1).gif

#コール スタックが行 1 を実行すると、コールバック関数が Promise キューに入れられます。 stack 行 2 が実行されると、コールバック関数が nextTick キューに入れられます。

2 行目以降のコードを実行する必要はありません。

コントロールはイベント ループに入り、そこで nextTick キューが Promise キューよりも優先されます (これが Node.js ランタイムの仕組みです)。

イベント ループは、nextTick キュー コールバック関数を実行してから、Promise キュー コールバック関数を実行します。

コンソールには、「this is process.nextTick 1」、次に「this is Promise.resolve 1」と表示されます。

this is process.nextTick 1
this is Promise.resolve 1

Inference

nextTick キュー内のすべてのコールバック関数は、Promise キュー内のコールバック関数よりも前に実行されます。

上記の 2 番目の実験のより詳細なバージョンを説明しましょう。

福祉実験

コード

// index.js
process.nextTick(() => console.log("this is process.nextTick 1"));
process.nextTick(() => {
  console.log("this is process.nextTick 2");
  process.nextTick(() =>
    console.log("this is the inner next tick inside next tick")
  );
});
process.nextTick(() => console.log("this is process.nextTick 3"));

Promise.resolve().then(() => console.log("this is Promise.resolve 1"));
Promise.resolve().then(() => {
  console.log("this is Promise.resolve 2");
  process.nextTick(() =>
    console.log("this is the inner next tick inside Promise then block")
  );
});
Promise.resolve().then(() => console.log("this is Promise.resolve 3"));

このコードには 3 つの process.nextTick() 呼び出しが含まれていますおよび 3 つの Promise.resolve() 呼び出しステートメント。各コールバック関数は、適切なメッセージをログに記録します。

但是,第二个 process.nextTick() 和第二个 Promise.resolve() 都有一个额外的 process.nextTick() 语句,每个都带有一个回调函数。

可视化

assets_YJIGb4i01jvw0SRdL5Bt_b32c606155c14ecfa60c8dc102f3bcf0_compressed (2).gif

为了加快对此可视化的解释,我将省略调用栈讲解。当调用栈执行所有 6 个语句时,nextTick 队列中有 3 个回调函数,Promise 队列中也有 3 个回调函数。没有其他要执行的内容后,控制权进入事件循环。

我们知道,nextTick 队列中具有高优先级。首先执行第 1 个回调函数,并将相应的消息记录到控制台中。

接下来,执行第 2 个回调函数,记录了第 2 条日志语句。但是,这个回调函数包含另一个对 process.nextTick() 的调用,该方法内的回调函数(译注:即() => console.log("this is the inner next tick inside next tick"))入队到nextTick 队列末尾。

然后 Node.js 执行第 3 个回调函数并将相应消息记录到控制台上。最初只有 3 个回调,但是因为第 2 次时向nextTick 队列又添加了一个,所以变成 4 个了。

事件循环将第 4 个回调函数推入调用栈,执行 console.log() 语句。

接着处理完 nextTick 队列之后,控制流程进入到 Promise 队列。Promise 队列与 nextTick 队列处理方式类似。

首先记录“Promise.resolve 1”,然后是“Promise.resolve 2”,这时因为调用 process.nextTick() 的原因,一个函数(译注:即 () => console.log("this is the inner next tick inside Promise then block") ) 被推入 nextTick 队列了。尽管如此,控制流程仍停留在 Promise 队列,因此还会继续执行队列中的其他函数。然后我们得到“Promise.resolve 3”,此时 Promise 队列为空。

Node.js 将再次检查微任务队列中是否有新的回调。由于 nextTick 队列中有一个回调,因此会执行这个回调,导致我们的最后一条日志输出。

this is process.nextTick 1
this is process.nextTick 2
this is process.nextTick 3
this is the inner next tick inside next tick
this is Promise.resolve 1
this is Promise.resolve 2
this is Promise.resolve 3
this is the inner next tick inside Promise then block

这可能是一个稍微高级的实验,但推论仍然相同。

推论

nextTick 队列中的所有回调函数优先于 Promise 队列中的回调函数执行。

使用 process.nextTick() 时要小心。过度使用此方法可能会导致事件循环饥饿,从而阻止队列中的其余部分运行。当存在大量的 nextTick() 调用时,I/O 队列是无法执行自己的回调函数的。官方文档建议使用 process.nextTick() 的两个主要场景:处理错误或在调用栈为空事件循环继续之前执行回调用。所以在使用 process.nextTick() 时,要明智一些。

总结

实验表明,用户编写的所有同步 JavaScript 代码优先于异步代码执行,并且 nextTick 队列中的所有回调函数优先于 Promise 队列中的回调函数执行。

原文链接:Visualizing nextTick and Promise Queues in Node.js Event Loop,2023年3月30日,by Vishwas Gopinath

更多node相关知识,请访问:nodejs 教程

以上がノードイベントループ内のマイクロタスクキューの詳細な分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はjuejin.cnで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。