ノードイベントループにおけるイベント実行の順序

ノードイベントループにおけるイベント実行の順序

イベントループ

ブラウザ環境では、js には独自のイベント ループがあり、node 環境にも同様のイベント ループがあります。

ブラウザ環境イベントループ

まず、ブラウザのイベント ループを確認しましょう。

要約すれば:

まず、メインスレッドの同期コードが実行されます。同期コードの各行は実行スタックにプッシュされ、非同期コードの各行は非同期 API (タイマースレッド、Ajax スレッドなど) にプッシュされます。実行スタックに実行するコードがない場合、つまり現在のメインスレッドに同期コードがない場合、タスクキューは非同期タスクのマイクロタスクキューからマイクロタスクを取得し、タスクキューに入れて実行し、そのコールバック関数再び実行スタックに入れて実行しますマイクロタスクキューが空の場合、非同期タスクはマクロタスクから取得されてタスクキューに追加され実行スタックにプッシュされてコールバック関数を実行し、マクロタスク内の同期タスクと非同期タスクの検索を続けます。1 サイクルでイベント ループ (イベント ポーリング) が完了します。

ブラウザ環境での例:

例:

        コンソールログ("1");
        タイムアウトを設定する(() => {
            コンソールにログ出力します。
        }, 1);
        新しいPromise((res, rej) => {
            console.log("約束");
            res('PromiseRes')
        }).then(val => {
            コンソールログ(val);
        })
        コンソールログ("2");

分析:
まず、同期コードの最初の行を見つけて、実行のためにそれを直接スローしますISERES)は、この時点で同期コードが再び遭遇し、現在のメインスレッドの同期コードが実行されています。実行されたパリマスのプロミスのために、[マクロタスクのキューからマクロタスクタイマーを使用する必要があります。

ノード環境イベントループ

ノードでは、イベント ループは主に6 つのステージに分かれています。

外部データ入力 –> ポーリングフェーズ –> チェックフェーズ –> シャットダウンイベントコールバックフェーズ –> タイマーフェーズ –> I/Oコールバックフェーズ –> アイドルフェーズ –> ポーリングフェーズ… ループ開始

6つのステージ

写真はインターネットから

ここに画像の説明を挿入

  • タイマー フェーズ: タイマーのコールバック (setTimeout、setInterval) を実行するために使用されます。
  • I/Oコールバックフェーズ: 前のサイクルで実行されなかったいくつかのI/Oコールバックを処理します。
  • アイドル、準備フェーズ: ノードによって内部的にのみ使用されるため、必要ありません。
  • ポーリング フェーズ: 新しい I/O 時間を取得します。適切な条件下では、ノードはここでブロックされます。
  • チェックフェーズ: setImmediate() のコールバックを実行します。
  • コールバックのクローズフェーズ: ソケットのクローズタイムコールバックを実行する

主なステージ
タイマー:
タイマー フェーズは、setTimeout および setInterval コールバックを実行し、ポーリング フェーズによって制御されます。
同様に、ノード内のタイマーによって指定された時間は正確な時間ではなく、できるだけ早く実行されることしかできません。
投票:
投票フェーズでは、システムは次の 2 つの処理を実行します。
1. タイマーステージに戻り、コールバックを実行します。
2. I/Oコールバックを実行し、この段階に入るときにタイマーが設定されていない場合は、次の2つのことが起こります。

ポーリングキューが空でない場合は、コールバックキューを走査し、キューが空になるかシステム制限に達するまで同期的に実行します。ポーリングキューが空の場合は、次の2つのことが起こります。
1. 実行する必要があるsetImmediateコールバックがある場合、ポーリングフェーズは停止し、チェックフェーズに入り、コールバックを実行します。
2. 実行すべき setImmediate コールバックがない場合、コールバックがキューに追加されるまで待機し、コールバックをすぐに実行します。待機を防ぐためにタイムアウト設定もあります。もちろん、タイマーが設定されていてポーリング キューが空の場合は、タイマーがタイムアウトしたかどうかを判断します。タイムアウトした場合は、タイマー ステージに戻ってコールバックを実行します。

チェックステージ
setImmediate() のコールバックがチェック キューに追加されます。イベント ループのステージ図から、チェック ステージの実行順序はポーリング ステージの後であることがわかります。チェック ステージに入ると、ポーリングは何かあるかどうかを確認し、その後チェック ステージに進みます。ない場合は、直接タイマー ステージに進みます。

(1)setTimeoutとsetImmediate

これら 2 つは非常に似ていますが、主な違いは呼び出しのタイミングです。

setImmediate は、ポーリング フェーズ、つまりチェック フェーズが完了したときに実行されるように設計されており、チェック フェーズでのみ実行されます。
setTimeout は、ポーリング フェーズがアイドル状態で、設定された時間に達したときに実行されるように設計されていますが、タイマー フェーズで実行されます。つまり、現在のスレッドには実行可能な他の同期タスクがなく、タイマーはタイマー フェーズで実行されます。

これら 2 つの実行のタイミングは、次の前または後のいずれかになります。
例1:

// // 非同期タスク内のマクロタスク setTimeout(() => {
    console.log('===setTimeout===');
},0);
setImmediate(() => {
    console.log('===setImmediate===')
})

ここに画像の説明を挿入

繰り返し実行した結果は異なり、ランダム感があ​​ります。その理由は主にsetTimeoutの実装コードに関係しています。timeパラメータを渡さなかったり、0に設定したりした場合、nodejsは1、つまり1msの値を取ります(ブラウザ側の値はもっと大きい場合があり、ブラウザによっても異なります)。したがって、コンピュータのCPUが1ms以内にタイマーフェーズを実行できるほど強力である場合、時間遅延が要件を満たさないためコールバックは実行されず、2回目の実行まで待つことしかできず、setIntervalが最初に実行されます。
上記のランダムな現象は、CPU が同じタスクを複数回実行するのに要する時間のわずかな差によって発生し、1 ミリ秒以内で変動する可能性があります。
通常、setTimeoutが0の場合、setImmediateの前に実行されます。

例2:
渡した値がタイマー実行のコールバック時間よりも大きい場合、次のイベントループでタイマーが直接実行されます。

タイムアウトを設定する(() => {
    console.log('===setTimeout===');
},10);
setImmediate(() => {
    console.log('===setImmediate===')
})

ここに画像の説明を挿入

例3:
上記のコードを i/o に配置すると、常に最初にチェックが行われ、次にタイマーがチェックされます。

定数 fs = require('fs');

fs.readFile("./any.js", (データ) => {
    タイムアウトを設定する(() => {
        console.log('===setTimeout===');
    },10);
    setImmediate(() => {
        console.log('===setImmediate===')
    })
});

ここに画像の説明を挿入

ループの最初のラウンドでは、ファイルが読み取られます。コールバックでは、チェックフェーズに入り、setImmediate を実行し、タイマーフェーズでタイマーが実行されます。
setimmediate と settimeout が同じ I/O ループ内で呼び出される場合は、setImmediate が常に最初に呼び出されます。

(2)プロセス.nextTick

この関数は実際にはイベント ループから独立しています。独自のキューがあります。各ステージが完了すると、nextTick キューがある場合は、キュー内のすべてのコールバック関数がクリアされ、他のマイクロタスクの前に最初に実行されます。

例1:

タイムアウトを設定する(() => {
 コンソールログ('timer1')
 Promise.resolve().then(function() {
   コンソールログ('promise1')
 })
}, 0)
プロセス.nextTick(() => {
 コンソールログ('nextTick')
 プロセス.nextTick(() => {
   コンソールログ('nextTick')
   プロセス.nextTick(() => {
     コンソールログ('nextTick')
     プロセス.nextTick(() => {
       コンソールログ('nextTick')
     })
   })
 })
})
// nextTick=>nextTick=>nextTick=>nextTick=>timer1=>promise1

例2:

定数 fs = require('fs');

fs.readFile("./any.js", (データ) => {
    process.nextTick(()=>console.log('process===2'))
    タイムアウトを設定する(() => {
        console.log('===setTimeout===');
    },10);
    setImmediate(() => {
        console.log('===setImmediate===')
    })
});
process.nextTick(()=>console.log('process===1'))

ここに画像の説明を挿入

練習例

非同期関数 async1() {
    コンソールログ('2')
    //awaitが終了するまで待機しますが、次のマイクロタスクawait async2()に入るため、それ以上実行されません。
    コンソールログ('9')
  }
   
   関数async2() {
    コンソールログ('3')
  }
   
  コンソールログ('1')
   
  setTimeout(関数() {
    コンソールログ('11')
  }, 0)
   
  setTimeout(関数() {
    コンソールログ('13')
  }, 300)
   
  setImmediate(() => console.log('12'));
   
  process.nextTick(() => console.log('7'));
   
  非同期1();
   
  process.nextTick(() => console.log('8'));
   
  新しいPromise(関数(resolve) {
    コンソールログ('4')
    解決する();
    コンソールログ('5')
  }).then(関数() {
    コンソールログ('10')
  })
   
  コンソールログ('6')

分析:
上記の順序はシリアル番号の順序です。
初版1号:
前に 2 つの関数宣言があり、いずれも同期コードのこの行 1 を直接出力します。
印刷2:
1 を出力した後、すべてのコードは非同期です。非同期タスク キューに追加され、async1 関数に直接呼び出されます。この関数では 2 が出力されます。
プリント3:
async1 関数は async await 関数なので、async2 関数の実行を待機する偽装された同期操作でもあります。async2 が実行された後、await は promise の then 操作を受け入れるため、9 は直接印刷されません。そのため、promise に属するコールバック操作はマイクロタスクであり、マイクロタスク キューに追加されます。
プリント4:
process.nextTick はマイクロタスクなので、promise の実行を継続し、4 を出力します。
プリント5:
resolve() のコールバックはすぐには実行されず、マイクロタスクに属します。マイクロタスク キューに追加されるため、5 が出力されます。
プリント6:
最後のメインスレッド同期コードは 6 を出力します。
印刷7と8:
process.nextTick は他のタイマーよりも優先度が高いため、コールバック関数が直接実行され、7 と 8 が出力されます。
プリント9、10:
このとき、マイクロタスク キュー内のマイクロタスクを実行する必要があります。現在、9 と 10 の 2 つがあります。順番に、最初に 9 が印刷され、次に 10 が印刷されます。
プリント11、12:
setTimeout は 0 秒で、setImmediate よりも早いです。実行順序では、最初に 11 が印刷され、後で 12 が印刷されます。
プリント13:
setTimeout が 300ms の関数は 13 を出力します。

例:

非同期関数 async1() {
    コンソールログ('2')
    //awaitが終了するまで待機しますが、次のマイクロタスクawait async2()に入るため、それ以上実行されません。
    コンソールログ('9')
  }
   
   関数async2() {
    コンソールログ('3')
  }
   
  コンソールログ('1')
   
  setTimeout(関数() {
    コンソールログ('11')
    タイムアウトを設定する(() => {
        コンソールログ('11-1');
    },100);
    setImmediate(() => {
        コンソールログ('11-2')
    })
  }, 0)
   
  setTimeout(関数() {
    コンソールログ('13')
    タイムアウトを設定する(() => {
        コンソールログ('15');
    },10);
    setImmediate(() => {
        コンソールログ('14')
    })
  }, 300)
  setImmediate(() => console.log('12'));
  process.nextTick(() => console.log('7'));
  非同期1();
   
  process.nextTick(() => console.log('8'));
   
  新しいPromise(関数(resolve) {
    コンソールログ('4')
    解決する();
    コンソールログ('5')
  }).then(関数() {
    コンソールログ('10')
  })
   
  コンソールログ('6')

要約:

ノードイベントループにおけるイベント実行順序に関する記事はこれで終了です。ノードイベント実行順序の詳細については、123WORDPRESS.COM の過去の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

参考: https://www.cnblogs.com/everlose/p/12846375.html

以下もご興味があるかもしれません:
  • Node.js ストリームの ondata トリガーのタイミングと順序の調査

<<:  MySQL トランザクション分離レベルの表示と変更の例

>>:  Docker で Harbor パブリック リポジトリを構築する方法の例

推薦する

MySQL 接続クエリを本当に学びましたか?

1. 内部結合クエリの概要内部結合は、アプリケーションで非常に一般的な結合操作であり、通常はデフォ...

ウェブマスターが注目すべき、ウェブサイトのユーザビリティを向上させる 9 つのコード最適化のヒント

1. ロゴに代替テキストを追加するこれには 2 つの利点があります。スクリーン リーダーがロゴ画像の...

require loaderの実装原理の深い理解

序文Node は新しいプログラミング言語ではなく、JavaScript のランタイムに過ぎないとよく...

JavaScriptカルーセルの実装について

今日もとても実践的な事例です。名前を聞くだけで高度で難しそうですよね?今日はカルーセル画像の真髄を簡...

デカルト積原理を使用してMySQLで複数のテーブルをクエリする方法を簡単に説明します。

MySQL マルチテーブルクエリ (直積原理)まず、データが使用するテーブルを決定します。デカルト...

Docker で Maven プロジェクトをより速くビルドする

目次I. 概要2. 従来の多段階イメージ構築3. Buildkitを使用してイメージをビルドする4....

Chromeの最小フォントサイズ制限12pxに対する最終的な解決策

ウェブサイトを作成するユーザーの多くが、このような問題に遭遇すると思います。Chrome のデフォル...

友達やグループを見つけるためのJavaScriptのLayim

現在、layuiの関係者はlayim友達検索ページの構造とスタイルを提供していません。私は個人的に非...

mysql5.5.28 のインストール チュートリアルは非常に詳細です。

参考までにmysql5.5.28のインストールチュートリアルです。具体的な内容は次のとおりです。イン...

画像のフェードインとフェードアウト効果を実現する js

この記事では、画像のフェードインとフェードアウトを実現するためのjsの具体的なコードを参考までに紹介...

Vue3 Vue イベント処理ガイド

目次1. 基本的なイベント処理2. 親コンポーネントにカスタムイベントを送信するマウス修飾子4. キ...

Linuxプロセス通信におけるFIFOの実装

FIFO通信(先入れ先出し)関連のないプロセス間の通信を可能にする FIFO 名前付きパイプ。パイプ...

JPQLに基づく純粋なSQL文方式の詳細な説明

JPQL は Java Persistence Query Language の略です。 Java ...

MySQLトランザクション処理の使用方法とサンプルコードの詳細な説明

MySQL トランザクション サポートは、MySQL サーバー自体にバインドされているのではなく、ス...

ES6 Promiseの使い方の詳細な説明

目次約束とは何ですか?拒否の使用法キャッチの使い方すべての使用法レースの使用約束とは何ですか? Pr...