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

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

イベントループ

ブラウザ環境では、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 パブリック リポジトリを構築する方法の例

推薦する

ウェブページ作成に役立つコード

<br />ホームページの右側にあるスクロールバーを削除するにはどうすればよいですか? ...

Dockerスペースがいっぱいでコンテナに入れない場合の解決策

トラブル発生が突然で、業務も迫っていたため、現場のスクリーンショットを撮る時間がありませんでしたので...

MySQL 結合バッファの原理

目次1. MySQL 結合バッファ2. JoinBufferCacheストレージスペースの割り当て3...

JavaScript が Jingdong の虫眼鏡効果を模倣

この記事では、Jingdongの虫眼鏡効果を実現するためのJavaScriptの具体的なコードを紹介...

CSS3 引用のソースと出典をマークする方法

疫病のせいで家にこもりきりで、頭がおかしくなりそうなので、パソコンを起動して頭を働かせてみました。今...

JavaScript でイベントのバブリングを防ぐ方法

注意すべき点は、イベントバブリング自体の特性上、メリットだけでなくデメリットも生じるということです。...

DockerコンテナでLNMPをコンパイルする例

目次1. プロジェクトの説明2. Nginxイメージの作成3. MySQLイメージの作成4. PHP...

抽選効果を実現するJavaScript

この記事では、宝くじマシンの効果を実現するためのJavaScriptの具体的なコードを参考までに共有...

JavaScriptの無限ループを検出して防止する方法の詳細な説明

目次序文for文の無限ループを修正while文の無限ループを修正要約する序文Js デッド ループはど...

Vue.jsはアイコンをクリックしてズームインし、

前回の記事では、Vue で画像の切り抜きや拡大・縮小、回転を実現する方法を紹介しました。今回は、アイ...

Vue バインディング オブジェクト、配列データを動的にレンダリングできないケースの詳細な説明

プロジェクトシナリオ: Dark Horse Vueプロジェクト管理の実践、製品分類の取得、拡張バー...

開発効率の向上に役立つ 56 個の実用的な JavaScript ツール関数

目次1. デジタルオペレーション(1)指定された範囲内で乱数を生成する2. 配列操作(1)配列の順序...

Pythonで書かれたWebアプリケーションをDockerでデプロイする実践

目次1. Dockerをインストールする2. コードを書く3. Dockerfileを書く4. 画像...

ウェブサイトを黒、白、グレーにする4つのコードの詳細な説明

2008年5月12日に四川省汶川市で発生した地震により、多くの命が失われ、遺憾なことと存じます。国務...

オブジェクトのプロパティを反復処理する際の TypeScript の問題

目次1. 問題2. 解決策1. オブジェクトをanyとして宣言する2. オブジェクトのインターフェー...