Nodejsのイベントループの仕組みを詳しく解説

青灯夜游
リリース: 2021-04-29 19:11:09
転載
2582 人が閲覧しました

この記事では、node のイベント ループ メカニズムを理解します。一定の参考値があるので、困っている友達が参考になれば幸いです。

Nodejsのイベントループの仕組みを詳しく解説

フロントエンド開発は JavaScript と切り離すことができません。JavaScript は Web フロントエンド言語であり、主に Web 開発で使用され、ブラウザーによって解析および実行されます。 js の役割はフロントエンド開発に限定されず、サーバーサイド開発 (nodejs) にも使用できます。理想と野心を持ったフロントエンド担当者として、視野を広げてサーバーサイド開発言語をマスターしたい場合、nodejs は非常に良い選択です。

関連する推奨事項: 「nodejs チュートリアル

js 開発方法をマスターしているので、node を簡単に始めることができ、npm パッケージ管理ツールも簡単に使用できます。開発エクスペリエンスが大幅に向上します。 Nodejs は非同期ノンブロッキング I/O の動作方式で有名で、その処理メカニズムはイベント ループと呼ばれます。

ノードのイベントループの仕組みを理解することで、ノードのイベント処理方法や非同期イベントの実行タイミングをより深く理解できるようになります. この記事では主にnodejsのイベントループの仕組みを解説し、その後のノードの学習の基礎を作ります。

1. ノード VS JavaScript

前述したように、JavaScript は主に Web 開発で使用される Web フロントエンド言語であり、ブラウザによって解析されて実行されます。 , 一方、node .js は Chrome V8 エンジンに基づいた JavaScript 実行環境です。したがって、nodejs は言語、ライブラリ、またはフレームワークではなく、js 実行環境です。簡単に言うと、node は js コードを解析して実行できます。以前は、ブラウザのみが JS を解析して実行できましたが、現在では、node はブラウザなしで完全に JS を実行できるようになります。

node.js とブラウザー js の間には多くの違いがあります。たとえば、ブラウザーの js には ecmascript、BOM、および DOM が含まれますが、nodejs の js には BOM、DOM がなく、emcscript のみが含まれます。また、node の js 実行環境は、ファイルの読み取りと書き込み、ネットワーク サービスの構築、ネットワーク通信、http サーバーなど、js のサーバー レベルの操作 API をいくつか提供します。これらの API のほとんどは、コア モジュールにパッケージ化されています。また、nodeのイベントループの仕組みはブラウザjsのイベントループの仕組みとは異なります。

2. JavaScript イベント ループ

ブラウザでの JS イベント ループについてはすでに誰もがよく知っているので、比較のためにここで簡単に説明します。

Nodejsのイベントループの仕組みを詳しく解説

(フィリップ・ロバーツのスピーチ「助けてください、イベントループにはまってしまいました」から引用)

js が実行されると、同期タスクと非同期タスクはそれぞれ異なる実行環境に入ります。同期タスクはメインスレッド、つまりメイン実行スタックに入り、非同期タスク (ajax リクエスト、settimeout、setinterval、poromise.resolve()、など) タスクキューに入ります。 ajax リクエスト、settimeout、setinterval など、さまざまな非同期タスクがさまざまなタスク キューにプッシュされます。これらのタスクはマクロ タスク キュー (マクロ タスク) にプッシュされ、Promise 関数はマイクロ タスク キューにプッシュされます (マイクロタスク)。全体的なイベント ループ プロセスは次のとおりです。

  • 同期コードが実行されると、メイン実行スタックが空になり、非同期タスクを実行する準備が始まります。

  • メイン スレッドは、マイクロタスク キューが空かどうかを確認します。空でない場合は、キュー内のすべてのマイクロタスクを走査して実行し、マイクロタスク キューをクリアしてから、マクロのタスクキューを確認してください。マイクロタスク キューが空の場合は、次のステップに直接進みます。

  • メイン スレッドはマクロ タスク キューを走査し、マクロ タスク キュー内の最初のマクロ タスクを実行します。実行中にマクロ タスクまたはマイクロ タスクが発生した場合、処理を続行します。対応するタスク キューにプッシュします。マクロ タスクが実行されるたびに、マイクロ タスク キューを走査してクリアする必要があります。

  • レンダリング操作を実行し、ビューを更新します

  • 次のイベント ループを開始し、2 つのタスク キューがクリアされるまで上記の手順を繰り返します。

影響をさらに深くするために、小さな例を取り上げます。次のコードを見てください。何が出力されますか:

    var le=Promise.resolve(2);
    console.log(le)
    console.log('3')
    Promise.resolve().then(()=>{
    console.log('Promise1')  
    setTimeout(()=>{
        console.log('setTimeout2')
    },0)
    })
    setTimeout(()=>{
    console.log('setTimeout1')
    Promise.resolve().then(()=>{
        console.log('Promise2')    
    })
    },0);
ログイン後にコピー

上記のイベント ループ プロセスを使用して分析します:

  • js メイン プロセスの実行コードが Promise.resolve(2) に遭遇すると、はすぐに実行され、2 は Promise オブジェクトに変更され、console.log(le) は le 変数 print----->Promise {: 2};
  • を出力します。
  • console.log('3') , print ----->3
  • その後、実行を続行し、非同期マイクロタスク関数である Promise.resolve().then にプッシュされます。マイクロタスク スタック
  • 関数が setTimeout に遭遇し、それをマクロ タスク キューにプッシュします。この時点では、メイン プロセスは空です。
  • マイクロ タスク キューを確認し、Promise.resolve() を見つけます。次に、----->promise1 を出力し、タイマーに再度遭遇し、それをマクロ タスク キューの最後にプッシュします。マイクロ タスク キューは空です。
  • マクロ タスク キューを確認し、最初のマクロ タスクを実行し、----->setTimeout1 を出力し、再び Promise.resolve() に遭遇します。その後、次のマクロタスクを開始する前にそれをマイクロタスク キュー
  • にプッシュすると、マイクロタスクはクリアされます。 setTimeout1 が出力された後、マイクロタスク キューがチェックされるため、---> ;promise2
  • イベント ループの次のラウンドは、マクロ タスク キューの現在の最初のタスクを取得して実行し、その後出力して出力します。 -->setTimeout2. この時点で、マクロ タスク キューとマイクロ タスク キューの両方がクリアされています。 、イベント ループが終了します

したがって、出力結果は次のようになります: Promise { : 2}、3、promise1、setTimeout1、promise2、setTimeout2。

ブラウザでの実行結果は以下の通りです:

Nodejsのイベントループの仕組みを詳しく解説

3. ノードイベントループ

があります。ノードのイベント ループの合計 6 ステージ。この 6 つのステージは、イベント処理が完了するまでイベント ループ内で順番に実行されます。 6 つのステージのシーケンス図は次のとおりです。

Nodejsのイベントループの仕組みを詳しく解説

六个阶段分别是:

  • timers 阶段:这个阶段执行timer(setTimeout、setInterval)的回调
  • I/O callbacks 阶段:执行一些系统操作的回调(比如网络通信的错误回调);
  • idle, prepare 阶段:仅node内部使用,可忽略
  • poll 阶段:获取新的I/O事件, 适当的条件下node将阻塞在这里
  • check 阶段:执行 setImmediate() 的回调
  • close callbacks 阶段:执行 socket 的 close 事件回调,如果一个socket或handle被突然关掉(比如socket.destroy()),close事件将在这个阶段被触发

事件循环中,每当进入某一个阶段,都会从该阶段对应的回调队列中取出函数去执行。当队列为空或者执行的回调函数数量到达系统设定的阈值,该阶段就会终止,然后检查NextTick队列和微任务队列,将其清空,之后进入下一个阶段。

这里面比较关键的是poll阶段:

  • poll队列不为空的时候,事件循环会遍历队列并同步执行回调,直到队列清空或执行回调数达到系统上限。
  • poll队列为空的时候,就会有两种情况:
    • 如果代码中存在setImmediate()回调,那么事件循环直接结束poll阶段进入check阶段来执行check队列里的回调;
    • 如果不存在setImmediate()回调,会等待回调被加入到队列中并立即执行回调,这里同样会有个超时时间设置防止一直等待下去,如果规定时间内有定时器函数进入队列,则返回到timer阶段,执行定时器回调,否则在poll阶段等待回调进入队列。

同样的举个大大的,看看以下代码会输出什么:

console.log('start')
setTimeout(() => {
  console.log('timer1')
  Promise.resolve().then(function() {
    console.log('promise1')
  })
}, 0)
setTimeout(() => {
  console.log('timer2')
  Promise.resolve().then(function() {
    console.log('promise2')
  })
}, 0)
Promise.resolve().then(function() {
  console.log('promise3')
})
console.log('end')
ログイン後にコピー

利用node事件循环分析呗:

  • 先执行同步任务,打印start,end
  • 进入timer阶段前,清空NextTick和micro队列,所以打印promise3
  • 进入timer阶段,打印timer1,并发现有一个微任务,立即执行微任务,打印promise1
  • 仍然在timer阶段,执行下个宏任务,打印timer2,同样遇到微任务,立即执行,打印promise2

因此输出顺序是:start,end,promise3,timer1,promise1,timer2,promise2,如果能正确回答出来说明对node的循环机制有了大体的了解,实际node输出结果确实是这样:

Nodejsのイベントループの仕組みを詳しく解説

那如下代码会输出什么呢?

process.nextTick(function(){
    console.log(7);
});

new Promise(function(resolve){
    console.log(3);
    resolve();
    console.log(4);
}).then(function(){
    console.log(5);
});

process.nextTick(function(){
    console.log(8);
});
ログイン後にコピー

继续分析:

  • process.nextTick会将任务推进至nextTick队列,promise.then会把任务推至micro队列,上面提到过每次一个宏任务执行完,执行下一个宏任务之前需要清空nextTick队列和micro队列,同样的一个阶段执行完,进入下一个阶段之前也需要nextTick队列和micro队列,并且nextTick队列优先级高于micro队列
  • 先执行同步代码,打印3,4
  • 执行nextTick队列,打印7,8
  • 再执行micro队列,打印5

因此最终输出是:3,4,7,8,5,需要记住,process.nextTick 永远大于 promise.then的优先级

还有一个大家很容易混淆的点就是setTimout和setImmediate的执行时机,根据上面描述的node事件循环机制,setImmediate()应该在check阶段执行 与 而setTimeout在timer阶段执行,理论上setTimout比setImmediate先执行,看下面的代码:

setTimeout(() => console.log(1),0);
setImmediate(() => console.log(2));
ログイン後にコピー

执行结果是什么?1,2 还是 2,1,其实都有可能,看实际node运行的结果:

Nodejsのイベントループの仕組みを詳しく解説

可以看到两次执行的结果不一样,为什么呢?原因在于即使setTimeout的第二个参数默认为0,但实际上,Node做不到0秒就执行其回调,最少也要4毫秒。那么进入事件循环后,如果没到4毫秒,那么timers阶段就会被跳过,从而进入check阶段执行setImmediate回调,此时输出结果是:2,1;

如果进入事件循环后,超过4毫秒(只是个大概,具体值并不确定),setTimeout的回调会出现在timer阶段的队列里,回调将被执行,之后再进入poll阶段和check阶段,此时输出结果是:1,2

那如果两者在I/O周期内调用,谁先执行呢?看一下代码:

const fs = require('fs')

fs.readFile('./test.txt', 'utf8' , (err, data) => {
  if (err) {
    console.error(err)
    return
  }
  setTimeout(() => {
    console.log('timeout');
  }, 0);
  setImmediate(() => {
    console.log('immediate');
  });
})
ログイン後にコピー

实际上,node中输出的结果总是immediate先输出,timeout后输出。因为I/O回调是在poll阶段执行,当回调执行完毕之后队列为空,发现存在setImmediate的回调就会进入check阶段,执行完毕后,再进入timer阶段。

四、总结

本文结合代码示例,对node的事件循环机制做了比较详细描述。通过这篇文章,应该可以了解浏览器的事件循环机制是怎样的,node的循环机制是怎样的,以及nextTick和micro队列的优先级,setTimout和setImmediate执行时机等一些容易混淆的知识点。文章中不足和不对之处,欢迎在评论区交流讨论,一起探索,谢谢。

更多编程相关知识,请访问:编程入门!!

以上がNodejsのイベントループの仕組みを詳しく解説の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:juejin.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート