Heim  >  Artikel  >  Web-Frontend  >  Detaillierte Erläuterung des Ereignisschleifenmechanismus in NodeJS

Detaillierte Erläuterung des Ereignisschleifenmechanismus in NodeJS

青灯夜游
青灯夜游nach vorne
2021-04-29 10:47:252519Durchsuche

In diesem Artikel erfahren Sie mehr über den Ereignisschleifenmechanismus in node. Es hat einen gewissen Referenzwert. Freunde in Not können sich darauf beziehen. Ich hoffe, es wird für alle hilfreich sein.

Detaillierte Erläuterung des Ereignisschleifenmechanismus in NodeJS

Frontend-Entwicklung ist untrennbar mit JavaScript verbunden. JavaScript ist eine Web-Frontend-Sprache, die hauptsächlich in der Webentwicklung verwendet und vom Browser analysiert und ausgeführt wird. Die Rolle von js ist nicht nur auf die Front-End-Entwicklung beschränkt, sondern kann auch für die serverseitige Entwicklung verwendet werden – Nodejs. Wenn Sie als Front-End-Mensch mit Idealen und Ambitionen Ihren Horizont erweitern und eine serverseitige Entwicklungssprache beherrschen möchten, ist NodeJS eine sehr gute Wahl.

Verwandte Empfehlungen: „nodejs Tutorial

Da Sie die js-Entwicklungsmethode beherrschen, ist der Einstieg in Node einfach, und das NPM-Paketverwaltungstool verbessert auch die Entwicklungserfahrung erheblich. Nodejs ist bekannt für seine asynchrone, nicht blockierende E/A-Arbeitsmethode und sein Verarbeitungsmechanismus wird als Ereignisschleife bezeichnet.

Wenn Sie den Knotenereignisschleifenmechanismus verstehen, können Sie die Ereignisverarbeitungsmethode des Knotens und den Ausführungszeitpunkt asynchroner Ereignisse besser verstehen. In diesem Artikel wird hauptsächlich der Ereignisschleifenmechanismus von NodeJS erläutert und die Grundlage für das anschließende Lernen des Knotens gelegt.

1. node VS javascript

Wie bereits erwähnt, ist Javascript eine Web-Front-End-Sprache, die hauptsächlich in der Webentwicklung verwendet wird und vom Browser analysiert und ausgeführt wird, während node.js eine darauf basierende JavaScript-Laufumgebung ist Die Chrome V8-Engine ist also keine Sprache, keine Bibliothek, kein Framework, sondern eine JS-Laufzeitumgebung. Einfach ausgedrückt: Node kann JS-Code analysieren und ausführen. Früher konnten nur Browser JS analysieren und ausführen, aber jetzt kann Node dafür sorgen, dass JS vollständig ohne den Browser ausgeführt wird.

Es gibt viele Unterschiede zwischen node.js und Browser-js. Beispielsweise enthält js im Browser Ecmascript, BOM und DOM, aber js in nodejs verfügt nicht über BOM und DOM, sondern nur über emcscript. Und die JS-Ausführungsumgebung des Knotens bietet einige Betriebs-APIs auf Serverebene für JS, z. B. Lesen und Schreiben von Dateien, Aufbau von Netzwerkdiensten, Netzwerkkommunikation, http-Server usw. Die meisten dieser APIs sind in Kernmodule gepackt. Darüber hinaus unterscheidet sich der Ereignisschleifenmechanismus des Knotens vom Ereignisschleifenmechanismus des Browser-JS.

2. JavaScript-Ereignisschleife

Jeder ist sich bereits über die js-Ereignisschleife im Browser im Klaren. Zum Vergleich werde ich sie hier kurz erwähnen.

Detaillierte Erläuterung des Ereignisschleifenmechanismus in NodeJS

(Zitiert aus der Rede von Philip Roberts „Hilfe, ich stecke in einer Ereignisschleife fest“)

Bei der Ausführung von js gelangen synchrone und asynchrone Aufgaben in unterschiedliche Ausführungsumgebungen. Synchrone Aufgaben gelangen in den Hauptthread, dh in den Hauptausführungsstapel, und asynchrone Aufgaben (Ajax-Anforderungen, Settimeout, Setinterval, Poromise.resolve () usw.) ) Geben Sie die Aufgabenwarteschlange ein. Verschiedene asynchrone Aufgaben werden in verschiedene Aufgabenwarteschlangen verschoben, z. B. Ajax-Anforderungen, Settimeout, Setinterval usw. Diese Aufgaben werden in die Makroaufgabenwarteschlange (Makroaufgabe) verschoben, während die Promise-Funktion in die Mikroaufgabenwarteschlange verschoben wird ( Mikroaufgabe). Der gesamte Ereignisschleifenprozess ist wie folgt:

  • Wenn der synchrone Code ausgeführt wird, wird der Hauptausführungsstapel leer und die Vorbereitungen für die Ausführung asynchroner Aufgaben beginnen.

  • Der Hauptthread prüft, ob die Mikrotask-Warteschlange leer ist. Wenn sie nicht leer ist, durchläuft er alle Mikrotasks in der Warteschlange, um sie auszuführen, löscht die Mikrotask-Warteschlange und überprüft dann die Makrotask-Warteschlange. Wenn die Mikrotask-Warteschlange leer ist, fahren Sie direkt mit dem nächsten Schritt fort.

  • Der Hauptthread durchläuft die Makroaufgabenwarteschlange und führt die erste Makroaufgabe in der Makroaufgabenwarteschlange aus. Wenn er während der Ausführung auf eine Makroaufgabe oder Mikroaufgabe stößt, wird er diese weiterhin in die entsprechende Aufgabenwarteschlange verschieben Sobald eine Makroaufgabe ausgeführt wird, muss die Mikrotask-Warteschlange durchlaufen und geleert werden.

  • Führen Sie den Rendervorgang durch und aktualisieren Sie die Ansicht. Starten Sie die nächste Ereignisschleife. Wiederholen Sie die obigen Schritte, bis die beiden Aufgabenwarteschlangen gelöscht sind

  • Um die Wirkung zu vertiefen, nehmen wir ein kleines Beispiel und sehen, was der folgende Code ausgibt:
  •     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);

    Verwenden Sie den obigen Ereignisschleifenprozess zur Analyse:

Der Ausführungscode des js-Hauptprozesses trifft auf Promise.resolve(2 ) wird sofort ausgeführt, wodurch 2 in ein Promise-Objekt umgewandelt wird, und dann gibt console.log(le) die le-Variable aus, print ----->Promise {: 2}

console .log( '3'), print ----->3

Fahren Sie dann mit der Ausführung fort und stoßen Sie auf Promise.resolve().then, eine asynchrone Mikrotask-Funktion, die auf den Mikrotask-Stapel verschoben wird
  • Die nächste Funktionsbegegnung setTimeout, schiebe es in die Makro-Task-Warteschlange, jetzt ist der Hauptprozess leer
  • Überprüfen Sie die Mikro-Task-Warteschlange und finden Sie Promise.resolve().then, also drucken Sie ----->promise1, stoßen Sie auf einen anderen Timer, schieben Sie ihn zum Makro Am Ende der Aufgabenwarteschlange ist die Mikrotask-Warteschlange leer
  • Überprüfen Sie die Makrotask-Warteschlange, nehmen Sie die erste auszuführende Makrotask, drucken Sie ----->setTimeout1, stoßen Sie auf Promise.resolve().dann erneut drücken zu microtask Die Warteschlange
  • löscht definitiv die Mikrotasks, bevor die nächste Makrotask gestartet wird. Daher wird nach dem Drucken von setTimeout1 die Mikrotask-Warteschlange überprüft, also --->promise2
  • Die nächste Runde der Ereignisschleife wird die Makrotask-Warteschlange übernehmen. Die aktuelle erste Aufgabe wird ausgeführt, also wird print----->setTimeout2 gedruckt. An diesem Punkt werden sowohl die Makro-Task- als auch die Mikro-Task-Warteschlange gelöscht und die Ereignisschleife endet. Das Ausgabeergebnis lautet also: Versprechen {: 2 }, 3, Versprechen1, setTimeout1, Versprechen2, setTimeout2.
  • Die Ausführungsergebnisse im Browser sind wie folgt:
3. Knoten-Ereignisschleife

Die Ereignisschleife des Knotens besteht aus sechs Phasen. In einer Ereignisschleife werden diese sechs Phasen nacheinander ausgeführt Ereignisverarbeitung abgeschlossen. Das Ablaufdiagramm der sechs Stufen sieht wie folgt aus:

Detaillierte Erläuterung des Ereignisschleifenmechanismus in 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输出结果确实是这样:

Detaillierte Erläuterung des Ereignisschleifenmechanismus in 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运行的结果:

Detaillierte Erläuterung des Ereignisschleifenmechanismus in 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执行时机等一些容易混淆的知识点。文章中不足和不对之处,欢迎在评论区交流讨论,一起探索,谢谢。

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

Das obige ist der detaillierte Inhalt vonDetaillierte Erläuterung des Ereignisschleifenmechanismus in NodeJS. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:juejin.cn. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen