Nodeイベントループの包括的な理解

Nodeイベントループの包括的な理解

ノードイベントループ

Node の基盤となる言語は C++ 言語であるlibuvです。これは、基盤となるオペレーティング システムを操作するために使用され、オペレーティング システム インターフェイスをカプセル化します。 Node のイベント ループもlibuvで記述されているため、Node のライフサイクルはブラウザーのライフサイクルとは異なります。

Node はオペレーティング システムを処理するため、イベント ループは比較的複雑で、独自の API がいくつかあります。
イベント ループは、オペレーティング システムによって微妙に異なります。これにはオペレーティング システムに関する知識が必要ですが、ここでは説明しません。今回はJSメインスレッドでのNodeの動作処理のみ紹介します。その他のノードスレッドは当面拡張されません。

イベントループ図

約束通り、写真も載せますので、皆さんをハラハラさせません。下の図を理解すれば、イベント ループを学習したことになります。

イベントループ図

イベントループ図 - 構造

皆様に概要をご理解いただくために、ディレクトリ構造図を以下に示します

目次

次に、詳しくお話ししましょう。

メインスレッド

メインスレッド

上の図では、いくつかのカラーブロックの意味は次のとおりです。

  • main : エントリファイルを開始し、メイン関数を実行します
  • event loop : イベントループに入るかどうかを確認します 他のスレッドに保留中のタスクがあるかどうかを確認します 他のタスクがまだ進行中かどうかを確認します (タイマー、ファイル読み取り操作など) 上記の状況が発生した場合、イベントループに入り、他のタスクを実行します
  • イベント ループのプロセス: タイマーからコールバックを閉じるまでのプロセスに従います。イベント ループに移動して、終了しているかどうかを確認します。終了していない場合は、別のサークルに進みます。
  • over :すべてが終わった、終わった

イベントループ

イベントループ

図中の灰色の円はオペレーティング システムに関連しており、この章の分析の焦点では​​ありません。黄色とオレンジ色の円、および中央のオレンジ色のボックスに特に注意してください。

イベント ループの各ラウンドを「サイクル」と呼びます。これは「ポーリング」または「ティック」とも呼ばれます。

サイクルは 6 つの段階を経ます。

  1. タイマー: タイマー (setTimeout、setInterval などのコールバック関数が格納されます)
  2. 保留中のコールバック
  3. アイドル準備
  4. poll: ポーリング キュー (タイマーとチェック以外のコールバックはここに保存されます)
  5. check: チェックフェーズ (setImmediate を使用するコールバックは直接このキューに入ります)
  6. コールバックを閉じる

今回は、上記の赤でマークされた 3 つの重要なポイントにのみ焦点を当てます。

仕組み

  • 各ステージはイベント キューを維持します。各円はイベント キューと考えることができます。
  • これは、最大 2 つのキュー (マクロ キューとマイクロ キュー) を持つブラウザーとは異なります。しかし、ノードには6つのキューがあります
  • キューに到着したら、キュー内に実行する必要があるタスクがあるかどうか (つまり、コールバック関数があるかどうか) を確認します。ある場合は、すべてが実行されてキューがクリアされるまで、順番に実行されます。
  • タスクがない場合は、次のキューに移動して確認します。すべてのキューがチェックされるまで、1 つのポーリングと見なされます。

このうち、 timerspending callbackidle prepareなどは実行後にpollキューに到着します。

タイマーキューの仕組み

タイマーは本当の意味でのキューではなく、内部にタイマーを格納します。
このキューに到達するたびに、タイマー スレッド内のすべてのタイマーがチェックされ、タイマー スレッド内の複数のタイマーが時系列順にソートされます。

チェックプロセス:各タイマーを順番に計算し、タイマーの開始から現在時刻までの時間がタイマー間隔パラメータ設定を満たしているかどうかを計算します(たとえば、1000msの場合、タイマーの開始から1分が経過したかどうかを計算します)。タイマー チェックに合格すると、そのコールバック関数が実行されます。

投票キューの仕組み

  • ポーリング内に実行する必要のあるコールバック関数がある場合、キューがクリアされるまでコールバックは順番に実行されます。
  • ポーリング内に実行する必要のあるコールバック関数がない場合、キューは空になります。ここで、他のキューにコールバックが表示されるまで待機します。
  • 他のキューでコールバックが発生した場合、プロセスはポーリングからオーバーに進み、このフェーズが終了して次のフェーズに入ります。
  • 他のキューにコールバックがない場合は、いずれかのキューにコールバックがあるまでポーリング キューで待機し続け、その後動作します。 (これは怠け者のやり方です)

イベントフローの例

タイムアウトを設定する(() => {
  console.log('オブジェクト');
}, 5000)
コンソールにログ出力します。

上記コードのイベントフロー

メインスレッドに入り、setTimeout() を実行します。コールバック関数は非同期タスクとして非同期キュータイマーに配置され、当面は実行されません。

  • 下に進み、タイマー後にコンソールを実行して「node」を出力します。
  • イベント ループがあるかどうかを判断します。はい、ポーリングを実行します: タイマーから - 保留中のコールバックから - アイドル準備まで...
  • ループを停止して待機するには、ポーリング キューに移動します。
  • まだ 5 秒が経過していないため、タイマー キューにはタスクがないため、ポーリング キューに留まり、他のキューをポーリングしてタスクがあるかどうかを確認します。
  • 5 秒後、setTimeout のコールバックがタイマーに挿入されます。ルーチン ポーリングはタイマー キューにタスクがあるかどうかをチェックし、チェックしてコールバックを閉じた後にタイマーに到達します。タイマー キューをクリアします。
  • ポーリングを続行し、イベント ループがまだ必要かどうかを尋ねて待機します。必要でない場合は、到達して終了します。

この問題を理解するには、次のコードとプロセス分析を見てください。

setTimeout(関数t1() {
  コンソールログに'setTimeout'と入力します。
}, 5000)
console.log('ノードのライフサイクル');

定数 http = require('http')

定数サーバー = http.createServer(関数 h1() {
  console.log('リクエストコールバック');
});

サーバー.listen(8080)

コード分​​析は次のとおりです。

  • 通常どおり、最初にメインスレッドを実行し、「ノードのライフサイクル」を出力し、http を導入して http サービスを作成します。
  • 次に、イベント ループは非同期タスクがあるかどうかを確認し、タイマー タスクと要求タスクを見つけます。そこでイベント ループに入ります。

6 つのキューにタスクがない場合、タスクはポーリング キューで待機します。以下のように表示されます。

  • 5 秒後、タイマーにタスクがあり、プロセスはポーリングから解放され、チェックおよびクローズ コールバック キューを通過して、イベント ループに到達します。
  • イベント ループは、非同期タスクがあるかどうかを確認し、タイマー タスクと要求タスクを見つけます。それで、再びイベント ループに入ります。
  • タイマー キューに到達し、コールバック関数タスクが見つかると、コールバックを順番に実行し、タイマー キューをクリアし (もちろん、5 秒後にはコールバックが 1 つしかないため、直接実行できます)、「setTimeout」を出力します。下記の通り

  • タイマー キューをクリアした後、ポーリングはポーリング キューまで続行されます。ポーリング キューは空になったため、ここで待機します。
  • その後、ユーザー リクエストが届いたと想定して、h1 コールバック関数がポーリング キューに配置されます。したがって、poll には実行する必要のあるコールバック関数があり、poll キューがクリアされるまでコールバックは順番に実行されます。
  • ポーリングキューがクリアされます。このとき、ポーリングキューは空のキューであり、待機を継続します。

  • ノードスレッドは常にポーリングキューに保持されているため、長時間タスクがない場合、自動的に切断されて待機し(パフォーマンスに自信がない)、ポーリングプロセスを下向きに実行し、チェックとクローズコールバックの後にイベントループに到達します。
  • イベント ループに到達したら、非同期タスクがあるかどうかを確認し、リクエスト タスクがあることを確認します。 (この時点でタイマータスクは実行されているため、存在しなくなります)その後、再びイベントループに入り続けます。
  • 投票キューに到着し、再度待機中...
  • 長時間待機してもタスクが来ない場合は、自動的に偶数ループに切断されます (タスクがないループ状況についてもう少し追加します)
  • 再び投票キューに戻り、一時停止する

無限ループ...

イベント ループ フロー チャートを整理します。

注: 下の図の「タスクはありますか」という用語は、「このキューにタスクがありますか」という意味です。

イベントループプロセスの概要

典型的な例を使用してプロセスを検証してみましょう。

定数 startTime = 新しい Date();

setTimeout(関数f1() {
  console.log('setTimeout', 新しい Date(), 新しい Date() - startTime);
}, 200)

console.log('ノードライフサイクル', startTime);

定数 fs = require('fs')

fs.readFile('./poll.js', 'utf-8', 関数 fsFunc(err, data) {
  const fsTime = 新しい日付()
  コンソールにログ出力します。
  (新しいDate() - fsTime < 300) の間 {
  }
  console.log('無限ループの終了', new Date());
});

3 回連続して実行し、次の結果を出力します。

実行プロセス分析:

  1. グローバルコンテキストを実行し、「ノードライフサイクル + 時間」を出力します。
  2. イベントループがあるかどうかを尋ねる
  3. はい、タイマー キューに入り、タイマーがあるかどうかを確認します (CPU 処理速度は正常で、現時点では 200 ミリ秒に達していません)
  4. ポーリングは poll に入りますが、ファイルはまだ完全には読み取られていません (たとえば、この時点では 20 ミリ秒しかかかりませんでした)。そのため、ポーリング キューは空であり、タスク コールバックはありません。
  5. ポーリングキューで待機しています...コールバックがあるかどうかを確認するためにポーリングを続けます
  6. ファイルが読み取られた後、ポーリングキューにはfsFuncコールバック関数があり、実行されて「fs + time」を出力します。
  7. while ループは 300 ミリ秒間停止します。
  8. 無限ループが 200 ミリ秒に達すると、f1 コールバックがタイマー キューに入ります。しかし、この時点ではポーリング キューは非常にビジー状態であり、スレッドを占有しているため、それ以上実行されません。
  9. 300ms後、ポーリングキューはクリアされ、出力は「無限ループの終了 + 時間」になります。
  10. イベントループはすぐにダウンします
  11. 再びタイマーの番となり、タイマー キュー内の f1 コールバックが実行されます。そこで「setTimeout + time」を見ました
  12. タイマーキューがクリアされ、ポーリングキューに戻ります。タスクがないので、しばらくお待ちください。

十分に待った後、イベント ループに戻ります。

イベント ループは、他の非同期タスクがないことを確認し、スレッドを終了し、プログラム全体を終了します。

チェックフェーズ

チェックフェーズ(setImmediate を使用するコールバックは直接このキューに入ります)

チェックキューの実際の仕組み

実際のキューには、実行されるコールバック関数のコレクションが含まれています。 [fn,fn]の形式に似ています。
チェックキューに到達するたびに、コールバック関数を順番にすぐに実行できます([fn1,fn2].forEach((fn)=>fn())]と同様)。

したがって、setImmediate はタイマーの概念ではありません。

面接に行って Node が関係する場合、次のような質問に遭遇する可能性があります。setImmediate と setTimeout(0) のどちらが速いですか?

setImmediate() と setTimeout(0)

  • setImmediate のコールバックは非同期であり、setTimeout のコールバックの性質と一致しています。
  • setImmediate コールバックはcheckキューにあり、setTimeout コールバックはtimersキューにあります (概念的には、実際にはタイマー スレッドにありますが、setTimeout はタイマー キューでチェック呼び出しを行います。詳細については、タイマーの動作を参照してください)。
  • setImmediate 関数が呼び出されると、コールバック関数はすぐにチェック キューにプッシュされ、次のイベント ループで実行されます。 setTimeout 関数が呼び出された後、タイマー スレッドはタイマー タスクを追加します。次にイベント ループが呼び出されると、タイマー フェーズでタイマー タスクが到着したかどうかがチェックされます。到着した場合は、コールバック関数が実行されます。

要約すると、setTimeout でもタイマー スレッドを開始して計算オーバーヘッドを増やす必要があるため、setImmediate は setTimeout(0) よりも高速に動作します。

両者の効果は似ています。しかし、執行順序は不明である

次のコードを確認してください。

タイムアウトを設定する(() => {
  コンソールログに'setTimeout'と入力します。
}, 0);

setImmediate(() => {
  コンソールにログ出力します。
});

何度も繰り返し実行した結果、実行効果は次のようになります。

不確かな順序

複数回実行した場合、2 つの console.log ステートメントの順序は固定されていないことがわかります。
これは、以下のコードでは 0 が入力されているにもかかわらず、setTimeout の最小間隔数が 1 であるためです。しかし、実際のコンピュータの実行は 1 ミリ秒として計算されます。 (ブラウザのタイマーとは異なることに注意してください。ブラウザでは、setInterval の最小間隔は 10 ミリ秒で、10 ミリ秒未満の場合は 10 に設定されます。デバイスの電源がオンの場合、最小間隔は 16.6 ミリ秒です。)

上記のコードでは、メインスレッドの実行中に setTimeout 関数が呼び出され、タイマースレッドがタイマータスクを追加します。 setImmediate 関数が呼び出されると、そのコールバック関数がすぐにチェック キューにプッシュされます。メインスレッドの実行が完了しました。

eventloop はタイマーとチェック キューにコンテンツがあることを判断すると、非同期ポーリングに入ります。

最初のケース: タイマーの時間が来たときに、残り 1 ミリ秒がない可能性があり、タイマー タスク間隔の条件が満たされないため、タイマーにコールバック関数がありません。チェックキューまで進みます。このとき、setImmediate のコールバック関数は長時間待機していたため、直接実行されます。次にイベントループがタイマー キューに到達すると、タイマーが満了し、setTimeout コールバック タスクが実行されます。したがって、順序は「setImmediate -> setTimeout」となります。

2 番目のケース: ただし、タイマー段階に達したときに 1 ミリ秒を超える可能性もあります。したがって、計算タイマー条件が満たされ、setTimeout コールバック関数が直接実行されます。次に、イベントループはチェック キューに移動して、setImmediate のコールバックを実行します。最終的な順序は「setTimeout -> setImmediate」です。

したがって、この 2 つの機能だけを比較する場合、2 つの実行順序の最終結果は、現在のコンピューターの動作環境と動作速度によって異なります

2つの時間差の比較コード

------------------setTimeout テスト:-------------------
i = 0 とします。
コンソールで時間を設定します。
関数テスト() {
  (i < 1000) の場合 {
    setTimeout(テスト、0)
    私は++
  } それ以外 {
    コンソールのtimeEnd('setTimeout');
  }
}
テスト();

------------------setImmediate テスト:-------------------
i = 0 とします。
コンソールで時間を設定します。
関数テスト() {
  (i < 1000) の場合 {
    setImmediate(テスト)
    私は++
  } それ以外 {
    コンソールのtimeEnd()関数は、次のようになります。
  }
}
テスト();

実行して時間差を観察します。

setTimeoutとsetImmediateの間の時間差

setTimeoutはsetImmediateよりも時間がかかることがわかります。

これは、setTimeout がメイン コードの実行時間を消費するだけではないからです。また、タイマー キューには、タイマー スレッド内のスケジュールされた各タスクの計算時間があります。

ポーリングキューに関する面接の質問(タイマー、ポーリング、チェックの実行順序の調査)

上記のイベント ループ図を理解していれば、次の質問は難しくないでしょう。

// 次のコードの実行順序について説明してください。どれが最初に印刷されるでしょうか?
定数 fs = require('fs')
fs.readFile('./poll.js', () => {
  setTimeout(() => console.log('setTimeout'), 0)
  setImmediate(() => console.log('setImmediate'))
})

上記のコード ロジックが何回実行されても、setImmediate は常に最初に実行されます。

まずsetImmediateを実行する

各 fs 関数のコールバックがポーリング キューに配置されるためです。プログラムがポーリング キューに保持されている場合、コールバックが直ちに実行されます。
コールバックで setTimeout 関数と setImmediate 関数が実行されると、チェック キューがすぐにコールバックに追加されます。
コールバックが実行された後、他のキューをポーリングしてコンテンツがあるかどうかを確認し、プログラムはポーリングキューの保持を終了して下方向に実行します。
投票後の次のフェーズはチェックです。したがって、下方向のプロセスでは、チェック フェーズのコールバックが最初に実行され、つまり、setImmediate が最初に出力されます。
次のサイクルでは、タイマー キューに到達すると、setTimeout タイマーが条件を満たしているかどうかが確認され、タイマー コールバックが実行されます。

次へチェックと約束

マクロタスクについて話した後は、ミクロタスクについて話しましょう。

  • どちらも非同期のマイクロタスクを実行する「マイクロキュー」です。
  • これら 2 つはイベント ループの一部ではなく、プログラムは関連するタスクを処理するために追加のスレッドを開始しません。 (理解: ネットワーク リクエストがプロミスで送信されると、ネットワーク リクエストによって開かれるネットワーク スレッドであり、プロミスのマイクロタスクとは関係ありません)
  • マイクロキューを設定する目的は、一部のタスクを「すぐに」または「すぐに」優先順位付けすることです。
  • Promiseと比較すると、nextTickはより高いレベルにあります。

次のティック式

プロセス.nextTick(() => {})

約束の表現

Promise.resolve().then(() => {})

イベントループに参加するにはどうすればいいですか?

イベント ループでは、各コールバックを実行する前に、nextTick と promise を順番にクリアします。

// まず次のコードの実行順序を検討してください setImmediate(() => {
  コンソールにログ出力します。
});

プロセス.nextTick(() => {
  console.log('nextTick 1');
  プロセス.nextTick(() => {
    console.log('nextTick 2');
  })
})

console.log('グローバル');


Promise.resolve().then(() => {
  console.log('約束1');
  プロセス.nextTick(() => {
    console.log('promise の nextTick');
  })
})

最終注文:

  1. グローバル
  2. 次へ1をチェック
  3. 次へ2をチェック
  4. 約束1
  5. 次へ約束を守る
  6. 即時設定

2つの質問:

上記に基づいて、検討して解決すべき 2 つの質問があります。

  1. 1. 非同期マクロタスクキューが実行されるたびに nextTick と promise をチェックしますか?それとも、マクロ タスク キュー内のコールバック関数が実行されるたびにチェックする必要がありますか?
  2. 2. ポーリングの保持フェーズ中に nextTick または Promise コールバックが挿入された場合、コールバックを実行するためにポーリング キューの保持は直ちに停止されますか?

上記の2つの質問については、以下のコードを参照してください。

タイムアウトを設定する(() => {
  コンソールログ('setTimeout 100');
  タイムアウトを設定する(() => {
    コンソールログ('setTimeout 100 - 0');
    プロセス.nextTick(() => {
      console.log('setTimeout 100 - 0 の nextTick');
    })
  }, 0)
  setImmediate(() => {
    console.log('setTimeout 100 で setImmediate を実行');
    プロセス.nextTick(() => {
      console.log('nextTick in setImmediate in setTimeout 100');
    })
  });
  プロセス.nextTick(() => {
    console.log('setTimeout100 内の nextTick');
  })
  Promise.resolve().then(() => {
    console.log('setTimeout100 の約束');
  })
}, 100)

定数 fs = require('fs')
fs.readFile('./1.poll.js', () => {
  console.log('ポーリング1');
  プロセス.nextTick(() => {
    console.log('投票のnextTick ======');
  })
})

タイムアウトを設定する(() => {
  コンソールログ('setTimeout 0');
  プロセス.nextTick(() => {
    console.log('setTimeout 内の nextTick');
  })
}, 0)

タイムアウトを設定する(() => {
  コンソールログ('setTimeout 1');
  Promise.resolve().then(() => {
    console.log('setTimeout1 の約束');
  })
  プロセス.nextTick(() => {
    console.log('setTimeout1 内の nextTick');
  })
}, 1)

setImmediate(() => {
  コンソールにログ出力します。
  プロセス.nextTick(() => {
    console.log('setImmediate 内の nextTick');
  })
});

プロセス.nextTick(() => {
  console.log('nextTick 1');
  プロセス.nextTick(() => {
    console.log('nextTick 2');
  })
})

console.log('グローバル ------');

Promise.resolve().then(() => {
  console.log('約束1');
  プロセス.nextTick(() => {
    console.log('promise の nextTick');
  })
})

/** 実行順序は以下のとおりです global ------
次へ1をチェック
次へ2をチェック
約束1
次へ約束を守る
setTimeout 0 // 問題の説明 1. 上記の nextTick と promise がないと、setTimeout と setImmediate の順序は不定です。これらがあれば、0 が必ず最初に開始されます。
// キューを実行する前に、nextTick と promise マイクロキューが最初にチェックされ、実行されることがわかります。nextTick in setTimeout
タイムアウト1を設定する
setTimeout1 の nextTick
setTimeout1 の約束
即時設定
setImmediate の nextTick
投票 1
次へ投票にチェックを入れる ======
タイムアウト100を設定する
setTimeout100 の nextTick
setTimeout100 の約束
setTimeout 100 の setImmediate
setImmediate の nextTick と setTimeout の 100
タイムアウト設定 100 - 0
setTimeout の nextTick 100 - 0
 */

上記のコードは同じ順序で複数回実行され、setTimeout と setImmediate の順序は変更されません。

実行順序と具体的な理由は次のとおりです。

  1. global : メインスレッド同期タスク、最初の実行はOK
  2. nextTick 1 : 非同期マクロタスクを実行する前に、非同期マイクロタスクをクリアします。nextTickは優先度が高く、1ステップ先に実行されます。
  3. nextTick 2 : 上記のコードを実行した後、別のnextTickマイクロタスクがすぐに実行されます。
  4. promise 1 : 非同期マクロタスクを実行する前に、非同期マイクロタスクをクリアします。promise は優先度が低いため、nextTick が完了したらすぐに実行されます。
  5. nextTick in promise : Promise キューをクリアするときに、 nextTick マイクロタスクに遭遇すると、それが実行され、すぐにクリアされます。
  6. setTimeout 0 : 最初の質問について説明します。上記の nextTick と promise がない場合、setTimeout と setImmediate だけがある場合、それらの実行順序は不確実です。それを手に入れたら、0から始めなければなりません。マクロ キューを実行する前に、 nextTick および promise マイクロ キューがチェックされ、順番に実行されることがわかります。すべてのマイクロキューが実行されると、setTimeout(0) のタイミングが熟し、実行されます。
  7. nextTick in setTimeout : 上記のコードを実行した後、別の nextTick マイクロタスクが最初に実行されます。[このコールバック関数内のマイクロタスクが同期タスクの直後に実行されるのか、それともマイクロタスク キューに入れられて次のマクロタスクが実行される前にクリアされるのかはわかりません。しかし、命令は即座に実行されたかのように見えます。しかし、私は後者を好みます。つまり、まずそれらをマイクロタスク キューに入れて待機し、次のマクロタスクが実行される前にそれらをクリアします。
  8. setTimeout 1 : マイクロタスクの実行には時間がかかるため、タイマー内の 2 つの setTimeout タイマー 0 と 1 が終了し、両方の setTimeout コールバックがキューに追加され、実行されました。
  9. nextTick in setTimeout1 : 上記のコードを実行した後、別の nextTick マイクロタスクがすぐに最初に実行されます[次のマクロタスクの前にマイクロタスクをクリアするためかもしれません]
  10. promise in setTimeout1 : 上記のコードを実行した後、別の Promise マイクロタスクがすぐに実行されます[次のマクロタスクの前にマイクロタスクをクリアするためかもしれません]
  11. setImmediate : ポーリングキューのコールバック時間が到来していないため、まずチェックキューに行き、キューをクリアして、すぐにsetImmediateコールバックを実行します。
  12. nextTick in setImmediate : 上記のコードを実行した後、別の nextTick マイクロタスクが直ちに実行されます[次のマクロタスクの前にマイクロタスクをクリアするためかもしれません]
  13. poll 1 : ポーリング キューが実際に成熟し、コールバックがトリガーされ、同期タスクが実行されます。
  14. nextTick in poll : 上記のコードを実行した後、別の nextTick マイクロタスクがすぐに最初に実行されます[次のマクロタスクの前にマイクロタスクをクリアするためかもしれません]
  15. setTimeout 100 : タイマータスクが到着すると、コールバックが実行されます。そして、nextTick と Promise をコールバック内のマイクロタスクにプッシュし、setImmediate コールバックをマクロタスクのチェックにプッシュします。また、タイマー スレッドを開始し、タイマーへの次のコールバック ラウンドの可能性を追加しました。
  16. nextTick in setTimeout100 : マクロタスクがダウンする前に、タイマーコールバックで新しく追加されたマイクロタスク - nextTick が最初に実行されます [ここで、次のマクロタスクの前にマイクロタスクをクリアするプロセスであることが判断できます]
  17. promise in setTimeout100 : タイマー コールバックで新しく追加されたマイクロタスクを直ちに実行します - Promise [nextTick の順序をクリアしてから Promise をクリアします]
  18. setImmediate in setTimeout 100 : 今回 setTimeout(0) の前に setImmediate が実行されるのは、プロセスがタイマーからチェック キューに逆戻りし、すぐに実行される setImmediate のコールバックがすでに存在するためです。
  19. nextTick in setImmediate in setTimeout 100 : 上記のコードを実行した後、別の nextTick マイクロタスクが実行されます。マイクロタスクは次のマクロタスクの前にクリアされます。
  20. setTimeout 100 - 0 : ポーリングは再びタイマーに戻り、100-0 のコールバックを実行します。
  21. nextTick in setTimeout 100 - 0 : 上記のコードを実行した後、別の nextTick マイクロタスクがあり、次のマクロタスクの前にマイクロタスクがクリアされます。

拡張: setImmediate があるのに、なぜ nextTick と Promise が必要なのでしょうか?

最初に設計されたとき、setImmediate はマイクロキューとして機能しました (実際はそうではありません)。設計者は、poll が実行された直後に setImmediate が実行されることを期待しています (もちろん、現時点では実際にその通りです)。したがって、名前はImmediate 、つまり立即という意味です。しかし、後で問題になるのは、poll には連続して実行される N 個のタスクがある可能性があり、実行中に setImmediate を実行することは不可能であるということです。ポーリングキューが停止されないため、プロセスは下方向に実行されません。

そこで、真のマイクロキューのコンセプトである nextTick が登場しました。ただし、このとき、immediate の名前が取られるので、名前は nextTick (次のティック) になります。イベント ループ中、キューを実行する前に、キューが空かどうかがチェックされます。 2つ目はPromiseです。

面接の質問

最後に、学習成果をテストするためのインタビューの質問です

非同期関数 async1() {
  console.log('非同期開始');
  async2() を待機します。
  console.log('非同期終了');
}

非同期関数 async2(){
  コンソールログ('async2');
}
console.log('スクリプトの開始');

タイムアウトを設定する(() => {
  コンソールログ('setTimeout 0');
}, 0)

タイムアウトを設定する(() => {
  コンソールログ('setTimeout 3');
}, 3)

setImmediate(() => {
  コンソールにログ出力します。
})

プロセス.nextTick(() => {
  コンソールにログ出力します。
})

非同期1();

新しいPromise((res) => {
  コンソールにログ出力します。
  解像度();
  コンソールにログ出力します。
}).then(() => {
  console.log('約束3');
});

console.log('スクリプト終了');

// 答えは次のとおりです // -
// -
// -
// -
// -
// -
// -
// -
// -
// -
// -
// -
/**
スクリプト開始
非同期開始
非同期2
約束1
約束2
スクリプト終了

次のチェック
非同期終了
約束3

// 次の 3 つの操作は、コンピューターの計算速度を確認するためのものです。
最速(上記の同期コード + マイクロタスク + タイマー操作を実行するのに 0 ミリ秒未満):
即時設定
タイムアウト0を設定する
タイムアウト3の設定

速度は中程度です(上記の同期コード + マイクロタスク + タイマー操作を実行するには 0 ~ 3 ミリ秒以上かかります)。
タイムアウト0を設定する
即時設定
タイムアウト3の設定

速度が遅い (上記の同期コード + マイクロタスク + タイマー操作の実行に 3 ミリ秒以上かかりました):
タイムアウト0を設定する
タイムアウト3の設定
即時設定
*/

マインドマップ

ノードライフサイクルのコアフェーズ

Node イベントループの包括的な理解に関するこの記事はこれで終わりです。Node イベントループの詳細については、123WORDPRESS.COM の以前の記事を検索するか、次の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • ノードイベントループとメッセージキューの分析
  • ノードのイベントループの実装を調べる
  • node.js イベントループの詳細な説明
  • ノードイベントループとプロセスモジュールの例の分析
  • node.js のイベントループの理解と応用例

<<:  MySQL ファジークエリステートメントコレクション

>>:  Windows 10 での MySQL 8.0.19 のインストールと設定のチュートリアル

推薦する

PostgreSQL データベースにおける varchar、char、text の比較に関する簡単な説明

以下のように表示されます。名前説明する文字可変(n)、varchar(n)長さ制限あり、可変長文字(...

タブ切り替えを実装するための HTML サンプル コード

タブ切り替えもプロジェクトではよく使われる技術です。一般的にタブ切り替えはjsやjqを使って実装され...

Zabbix 監視 Docker アプリケーション構成

コンテナの応用はますます一般的になっていますが、大量のコンテナをどのように管理すればよいのでしょうか...

中国語でのNginx設定パラメータの詳細な説明(負荷分散とリバースプロキシ)

PS: 最近、nginx を詳細に紹介している <<High-Performance ...

JavaScriptプロトタイプチェーンを理解する

目次1. プロトタイプとプロトタイプチェーンの平等関係を理解する2: プロトタイプとプロトタイプ チ...

IE アドレスバーのアイコン表示問題を解決する 3 つの手順

<br />この Web ページ制作スキル チュートリアルは、Web サイトのアイコンを...

React Nativeがシミュレータにリンクできない件について

React Native は、現在人気のオープンソース JavaScript ライブラリ React...

Alibaba Cloud ECS クラウド サーバー (Linux システム) は、MySQL をインストールした後にリモートで接続できません (落とし穴)

昨日、1年間使用していた Alibaba Cloud サーバーを購入しました。システムは Linux...

vue3.0 で要素を使用するための完全な手順

序文: vue3.0の要素フレームワークを使用します。要素はvue2.0をサポートしており、vue3...

Zabbix パスワードをリセットする方法 (ワンステップ)

問題の説明長い間アカウントパスワードを入力して Zabbix にログインしていないため、管理者パスワ...

JSコードコンパイラMonacoの使い方

序文私が必要としているのは、構文の強調表示、関数プロンプト、自動行折り返し、およびコードの折りたたみ...

MySQL 接続数を設定する方法 (接続数が多すぎる)

mysql使用中に接続数が超過していることが判明しました~~~~ [root@linux-node...

Vue が Web オンラインチャット機能を実現

この記事では、Webオンラインチャットを実装するためのVueの具体的なコードを参考までに紹介します。...

Linuxは、単一のIPをバインドするためにデュアルネットワークカードを実装するためにボンドを使用します。サンプルコード

ネットワークの高可用性を実現するには、複数のネットワーク カードを仮想ネットワーク カードにバインド...

CSS3 Flex エラスティックレイアウトのサンプルコードの詳細な説明

1. 基本概念 //任意のコンテナを Flex レイアウトとして指定できます。 。箱{ ディスプレイ...