Heim >Web-Frontend >js-Tutorial >Vertiefendes Verständnis des Node-Event-Loop-Mechanismus (EventLoop).

Vertiefendes Verständnis des Node-Event-Loop-Mechanismus (EventLoop).

青灯夜游
青灯夜游nach vorne
2023-03-16 20:11:321904Durchsuche

Der Hauptthread liest Ereignisse aus der „Aufgabenwarteschlange“. Dieser Prozess ist zyklisch, daher wird der gesamte Betriebsmechanismus auch als Ereignisschleife bezeichnet. Der folgende Artikel wird Ihnen helfen, den Eventloop in Node.js zu meistern. Ich hoffe, er ist hilfreich für Sie!

Vertiefendes Verständnis des Node-Event-Loop-Mechanismus (EventLoop).

Obwohl js im Browser und im node ausgeführt werden kann, sind ihre Ereignisschleifenmechanismen nicht dieselben. Und es gibt einen großen Unterschied. js可以在浏览器中执行又可以在node中执行,但是它们的事件循环机制并不是一样的。并且有很大的区别。

EventLoop机制概述

在说Node事件循环机制之前,我们先来讨论两个问题

为什么要学习事件循环机制?

学习事件循环可以让开发者明白JavaScript的运行机制是怎么样的。

事件循环机制做的是什么事情?

事件循环机制用于管理异步API的回调函数什么时候回到主线程中执行

Node.js采用的是异步IO模型。同步API在主线程中执行,异步API在底层的C++维护的线程中执行,异步API的回调函数也会在主线程中执行。【相关教程推荐:nodejs视频教程编程教学

在Javascript应用运行时,众多异步API的回调函数什么时候能回到主线程中调用呢?这就是事件环环机制做的事情,管理异步API的回调函数什么时候回到主线程中执行。

EventLoop的六个阶段

Node中的事件循环分为六个阶段。

在事件循环中的每个阶段都有一个队列,存储要执行的回调函数,事件循环机制会按照先进先出的方式执行他们直到队列为空。

这六个阶段都存储着异步回调函数,所以还是遵循先执行主线程同步代码,当同步代码执行完后再来轮询这六个阶段。

接下来,我们来详细看看这六个阶段里面存储的都是什么

Timers

Timers:用于存储定时器的回调函数(setlnterval,setTimeout)。

Pendingcallbacks

Pendingcallbacks:执行与操作系统相关的回调函数,比如启动服务器端应用时监听端口操作的回调函数就在这里调用。

idle,prepare

idle,prepare:系统内部使用。(这个我们程序员不用管)

Poll

Poll:存储1/O操作的回调函数队列,比如文件读写操作的回调函数。

在这个阶段需要特别注意,如果事件队列中有回调函数,则执行它们直到清空队列 ,否则事件循环将在此阶段停留一段时间以等待新的回调函数进入。

但是对于这个等待并不是一定的,而是取决于以下两个条件:

  • 如果setlmmediate队列(check阶段)中存在要执行的调函数。这种情况就不会等待。
  • timers队列中存在要执行的回调函数,在这种情况下也不会等待。事件循环将移至check阶段,然后移至Closingcallbacks阶段,并最终从timers阶段进入下一次循环。

Check

Check:存储setlmmediate的回调函数。

Closingcallbacks

Closingcallbacks:执行与关闭事件相关的回调,例如关闭数据库连接的回调函数等。

宏任务与微任务

跟浏览器中的js一样,node中的异步代码也分为宏任务和微任务,只是它们之间的执行顺序有所区别。

我们再来看看Node中都有哪些宏任务和微任务

宏任务

  • setlnterval

  • setimeout

  • setlmmediate

  • I/O

微任务

  • Promise.then

  • Promise.catch

  • Promise.finally

  • process.nextTick

node

Übersicht über den EventLoop-Mechanismus

Bevor wir über den Node-Ereignisschleifenmechanismus sprechen, wollen wir zunächst zwei Probleme besprechen 🎜

Warum sollten wir den Ereignisschleifenmechanismus lernen?

🎜Durch das Erlernen der Ereignisschleife können Entwickler verstehen, wie JavaScript funktioniert. 🎜

Was macht der Ereignisschleifenmechanismus?

🎜Der Ereignisschleifenmechanismus wird verwendet, um zu verwalten, wann die Rückruffunktion der asynchronen API zur Ausführung zum Hauptthread zurückkehrt. 🎜🎜Node.js verwendet ein asynchrones IO-Modell. Die synchrone API wird im Hauptthread ausgeführt, die asynchrone API wird in dem Thread ausgeführt, der vom zugrunde liegenden C++ verwaltet wird, und die Rückruffunktion der asynchronen API wird ebenfalls im Hauptthread ausgeführt. [Empfohlene verwandte Tutorials: nodejs-Video-Tutorial, Programmierunterricht]🎜🎜Wenn eine Javascript-Anwendung ausgeführt wird, wann kehren die Rückruffunktionen vieler asynchroner APIs zurück? die Hauptseite? Wie wäre es mit dem Aufrufen eines Threads? Dies ist die Aufgabe des Ereignisschleifenmechanismus, der verwaltet, wann die Rückruffunktion der asynchronen API zur Ausführung zum Hauptthread zurückkehrt. 🎜

Sechs Phasen von EventLoop

🎜Die Ereignisschleife in Node ist in sechs Phasen unterteilt. 🎜🎜🎜🎜in der Ereignisschleife Jede Stufe verfügt über eine Warteschlange zum Speichern der auszuführenden Rückruffunktionen, und der Ereignisschleifenmechanismus führt sie nach dem First-In-First-Out-Prinzip aus, bis die Warteschlange leer ist. 🎜🎜Diese sechs Stufen speichern alle asynchrone Rückruffunktionen. Daher ist es immer noch erforderlich, zuerst den Haupt-Thread-Synchronisationscode auszuführen und dann diese sechs Stufen abzufragen, nachdem der Synchronisationscode ausgeführt wurde. 🎜🎜Als nächstes werfen wir einen detaillierten Blick darauf, was in diesen sechs Phasen gespeichert ist🎜

Timer

🎜Timer : Rückruffunktion (setlnterval, setTimeout) zum Speichern von Timern. 🎜

Ausstehende Rückrufe

🎜Ausstehende Rückrufe: Führen Sie Rückruffunktionen im Zusammenhang mit dem Betriebssystem aus, z. B. beim Starten eines Servers -seitige Anwendung Hier wird die Rückruffunktion aufgerufen, die den Portbetrieb überwacht. 🎜

idle, vorbereiten

🎜idle, vorbereiten: wird intern vom System verwendet. (Wir Programmierer müssen uns darüber keine Sorgen machen)🎜

Poll

🎜Poll: Speichert Rückrufe für 1/O-Operationen Funktionswarteschlange, z. B. Rückruffunktionen für Lese- und Schreibvorgänge für Dateien. 🎜🎜In dieser Phase ist besondere Aufmerksamkeit geboten. Wenn sich in der Ereigniswarteschlange Rückruffunktionen befinden, werden diese ausgeführt, bis die Warteschlange geleert wird. , andernfalls bleibt die Ereignisschleife eine Zeit lang in dieser Phase und wartet auf den Eintritt einer neuen Rückruffunktion. 🎜🎜Aber dieses Warten ist nicht sicher, sondern hängt von den folgenden zwei Bedingungen ab: 🎜
  • Ob sich in der setlmmediate-Warteschlange eine aufrufende Funktion befindet, die ausgeführt werden soll (Prüfphase). In diesem Fall entfällt die Wartezeit.
  • In der Timer-Warteschlange muss eine Rückruffunktion ausgeführt werden, und in diesem Fall gibt es keine Wartezeiten. Die Ereignisschleife wechselt zur Prüfphase, dann zur Phase „Closingcallbacks“ und schließlich von der Timer-Phase zur nächsten Schleife.

Check

🎜Check: Speichert die Rückruffunktion von setlmmediate. 🎜

Closingcallbacks

🎜Closingcallbacks: Rückrufe im Zusammenhang mit Abschlussereignissen ausführen, z. B. Rückruffunktionen, die Datenbankverbindungen schließen Warten. 🎜

Makroaufgaben und Mikroaufgaben

🎜Gleiche wie js im Browser, node ist ebenfalls in Makroaufgaben und Mikroaufgaben unterteilt, die Ausführungsreihenfolge zwischen ihnen ist jedoch unterschiedlich. 🎜🎜Werfen wir einen Blick auf die Makroaufgaben und Mikroaufgaben in <code>Node🎜

Makroaufgaben

  • 🎜setlnterval🎜
  • 🎜setimeout🎜
  • 🎜setlmmediate🎜
  • 🎜I/O 🎜
  • Mikrotasks

    • 🎜Promise.then🎜
    • 🎜Promise.catch🎜
    • 🎜Promise.finally🎜
    • 🎜process.nextTick🎜
    • ul> 🎜Wie ist in node die Ausführungsreihenfolge von Mikrotasks und Makrotasks? 🎜

      Ausführungsreihenfolge von Mikrotasks und Makrotasks

      In node wird die Rückruffunktion von Microtask in die Mikrotask-Warteschlange gestellt, und die Rückruffunktion von Macrotask wird in die Makrotask-Warteschlange gestellt. node中,微任务的回调函数被放置在微任务队列中,宏任务的回调函数被放置在宏任务队列中。

      微任务优先级高于宏任务。当微任务事件队列中存在可以执行的回调函数时,事件循环在执行完当前阶段的回调函数后会暂停进入事件循环的下一个阶段,而会立即进入微任务的事件队列中开始执行回调函数,当微任务队列中的回调函数执行完成后,事件循环才会进入到下一个段开始执行回调函数。

      对于微任务我们还有个点需要特别注意。那就是虽然nextTick同属于微任务,但是它的优先级是高于其它微任务,在执行微任务时,只有nextlick中的所有回调函数执行完成后才会开始执行其它微任务。

      总的来说就是当主线程同步代码执行完毕后会优先清空微任务(如果微任务继续产生微任务则会再次清空),然后再到下个事件循环阶段。并且微任务的执行是穿插在事件循环六个阶段中间的,也就是每次事件循环进入下个阶段前会判断微任务队列是否为空,为空才会进入下个阶段,否则先清空微任务队列。

      下面我们用代码实操来验证前面所说的。

      代码实例

      先执行同步再执行异步

      Node应用程序启动后,并不会立即进入事件循环,而是先执行同步代码,从上到下开始执行,同步API立即执行,异步API交给C++维护的线程执行,异步API的回调函数被注册到对应的事件队列中。当所有同步代码执行完成后,才会进入事件循环。

      console.log("start");
      
      setTimeout(() => {
        console.log("setTimeout 1");
      });
      
      setTimeout(() => {
        console.log("setTimeout 2");
      });
      
      console.log("end");

      我们来看执行结果

      Vertiefendes Verständnis des Node-Event-Loop-Mechanismus (EventLoop).

      可以看到,先执行同步代码,然后才会进入事件循环执行异步代码,在timers阶段执行两个setTimeout回调。

      setTimeout一定会先于setImmediate执行吗

      我们知道setTimeout是在timers阶段执行,setImmediate是在check阶段执行。并且事件循环是从timers阶段开始的。所以会先执行setTimeout再执行setImmediate

      对于上面的分析一定对吗?

      我们来看例子

      console.log("start");
      
      setTimeout(() => {
        console.log("setTimeout");
      });
      
      setImmediate(() => {
        console.log("setImmediate");
      });
      
      const sleep = (delay) => {
        const startTime = +new Date();
        while (+new Date() - startTime < delay) {
          continue;
        }
      };
      
      sleep(2000);
      console.log("end");

      执行上面的代码,输出如下

      Vertiefendes Verständnis des Node-Event-Loop-Mechanismus (EventLoop).

      先执行setTimeout再执行setImmediate

      接下来我们来改造下上面的代码,把延迟器去掉,看看会输出什么

      setTimeout(() => {
        console.log("setTimeout");
      });
      
      setImmediate(() => {
        console.log("setImmediate");
      });

      我们运行了七次,可以看到其中有两次是先运行的setImmediate

      Vertiefendes Verständnis des Node-Event-Loop-Mechanismus (EventLoop).

      怎么回事呢?不是先timers阶段再到check阶段吗?怎么会变呢?

      其实这就得看进入事件循环的时候,异步回调有没有完全准备好了。对于最开始的例子,因为有2000毫秒的延迟,所以进入事件循环的时候,setTimeout回调是一定准备好了的。所以执行顺序不会变。但是对于这个例子,因为主线程没有同步代码需要执行,所以一开始就进入事件循环,但是在进入事件循环的时候,setTimeout的回调并不是一定完全准备好的,所以就会有先到check阶段执行setImmediate回调函数,再到下一次事件循环的timers阶段来执行setTimeout的回调。

      那在什么情况下同样的延迟时间,setImmediate回调函数一定会优先于setTimeout的回调呢?

      其实很简单,只要将这两者放到timers阶段和check阶段之间的Pendingcallbacks、idle,prepare、poll阶段中任意一个阶段就可以了。因为这些阶段完执行完是一定会先到check再到timers阶段的。

      我们以poll阶段为例,将这两者写在IO操作中。

      const fs = require("fs");
      
      fs.readFile("./fstest.js", "utf8", (err, data) => {
        setTimeout(() => {
          console.log("setTimeout");
        });
      
        setImmediate(() => {
          console.log("setImmediate");
        });
      });

      我们也来执行七次,可以看到,每次都是setImmediate先执行。

      Vertiefendes Verständnis des Node-Event-Loop-Mechanismus (EventLoop).

      所以总的来说,同样的延迟时间,setTimeout并不是百分百先于setImmediate

      Mikroaufgaben haben eine höhere Priorität als Makroaufgaben. Wenn sich in der Mikrotask-Ereigniswarteschlange eine ausführbare Rückruffunktion befindet, wird die Ereignisschleife angehalten und nach Ausführung der Rückruffunktion der aktuellen Stufe in die nächste Stufe der Ereignisschleife eingetreten. Anschließend tritt sie sofort in die Ereigniswarteschlange der Mikrotask ein, um mit der Ausführung des Rückrufs zu beginnen Wenn die Rückruffunktion in der Mikrotask-Warteschlange ausgeführt wird, tritt die Ereignisschleife in das nächste Segment ein und beginnt mit der Ausführung der Rückruffunktion. 🎜🎜Es gibt noch einen weiteren Punkt, auf den wir bei Mikroaufgaben besonders achten müssen. Das heißt, obwohl nextTick auch eine Mikrotask ist, hat sie eine höhere Priorität als andere Mikrotasks. Bei der Ausführung einer Mikrotask werden nur alle Rückruffunktionen in nextlick ausgeführt beginnen mit der Ausführung. 🎜🎜Wenn der Haupt-Thread-Synchronisationscode ausgeführt wird, werden im Allgemeinen zuerst die Mikrotasks gelöscht (wenn die Mikrotasks weiterhin Mikrotasks generieren, werden sie erneut gelöscht) und dann mit der nächsten Ereignisschleifenstufe fortgefahren. Und die Ausführung von Mikrotasks erfolgt zwischen den sechs Phasen der Ereignisschleife. Das heißt, jedes Mal, wenn die Ereignisschleife in die nächste Phase eintritt, wird festgestellt, ob die Mikrotask-Warteschlange leer ist Andernfalls werden die Mikrotasks zuerst gelöscht. 🎜🎜Lassen Sie uns anhand der Code-Praxis überprüfen, was wir oben gesagt haben. 🎜

      🎜Codebeispiel🎜

      🎜Führen Sie zuerst die Synchronisierung und dann asynchron aus🎜🎜🎜In Node Code>Nachdem die Anwendung gestartet wurde, tritt sie nicht sofort in die Ereignisschleife ein. Stattdessen wird der synchrone Code von oben nach unten ausgeführt. Die synchrone API wird sofort ausgeführt Der von C++ verwaltete Thread wird in der entsprechenden Ereigniswarteschlange registriert. Die Ereignisschleife wird erst eingegeben, nachdem die gesamte Ausführung des Synchronisierungscodes abgeschlossen ist. 🎜<pre class="brush:js;toolbar:false;">console.log("start"); setTimeout(() =&gt; { console.log(&quot;setTimeout&quot;); }); setImmediate(() =&gt; { console.log(&quot;setImmediate&quot;); }); Promise.resolve().then(() => { console.log("Promise.resolve"); }); console.log("end");</pre>🎜Werfen wir einen Blick auf die Ausführungsergebnisse🎜🎜<img src="https://img.php.cn/upload/article/000/000/024/8acad95dd96ea6ea6e891129b7f15a0e-1.png" alt="Vertiefendes Verständnis des Node-Event-Loop-Mechanismus (EventLoop). " loading="Lazy">🎜🎜Sie können sehen, dass zuerst der synchrone Code ausgeführt wird und dann die Ereignisschleife eingegeben wird, um den asynchronen Code auszuführen. Im <code werden zwei>setTimeout-Rückrufe ausgeführt >timers Stufe. 🎜

      🎜setTimeout wird definitiv vor setImmediate ausgeführt🎜🎜🎜Wir wissen, dass setTimeout in der timers-Stufe ausgeführt wird, setImmediate

    wird in der Phase check ausgeführt. Und die Ereignisschleife beginnt mit der timers-Phase. Daher wird zuerst setTimeout und dann setImmediate ausgeführt. 🎜🎜Ist die obige Analyse definitiv richtig? 🎜🎜Sehen wir uns ein Beispiel an🎜
    console.log("start");
    
    setTimeout(() => {
      console.log("setTimeout");
    });
    
    setImmediate(() => {
      console.log("setImmediate");
    });
    
    Promise.resolve().then(() => {
      console.log("Promise.resolve");
    });
    
    process.nextTick(() => {
      console.log("process.nextTick");
    });
    
    console.log("end");
    🎜Führen Sie den obigen Code aus, die Ausgabe ist wie folgt🎜🎜Vertiefendes Verständnis des Node-Event-Loop-Mechanismus (EventLoop).🎜🎜Führen Sie zuerst setTimeout und dann setImmediate aus 🎜🎜Als nächstes transformieren wir den obigen Code und ändern ihn den Verzögerer Entfernen Sie ihn und sehen Sie, wie die Ausgabe aussehen wird🎜
    // timers阶段
    setTimeout(() => {
      console.log("setTimeout");
    
      Promise.resolve().then(() => {
        console.log("setTimeout Promise.resolve");
      });
    });
    
    // check阶段
    setImmediate(() => {
      console.log("setImmediate");
      Promise.resolve().then(() => {
        console.log("setImmediate Promise.resolve");
      });
    });
    
    // 微任务
    Promise.resolve().then(() => {
      console.log("Promise.resolve");
    });
    
    // 微任务
    process.nextTick(() => {
      console.log("process.nextTick");
      Promise.resolve().then(() => {
        console.log("nextTick Promise.resolve");
      });
    });
    🎜Wir haben ihn sieben Mal ausgeführt, und Sie können sehen, dass zwei davon zuerst ausgeführt wurden setImmediate🎜🎜Vertiefendes Verständnis des Node-Event-Loop-Mechanismus (EventLoop).🎜🎜Was ist los? Ist es nicht zuerst die timers-Phase und dann die check-Phase? Wie könnte es sich ändern? 🎜🎜Tatsächlich hängt es davon ab, ob der asynchrone Rückruf beim Eintritt in die Ereignisschleife vollständig vorbereitet ist. Da es im ersten Beispiel eine Verzögerung von 2000 Millisekunden gibt, muss der Rückruf setTimeout bereit sein, wenn er in die Ereignisschleife eintritt. Die Reihenfolge der Ausführung wird sich also nicht ändern. Da der Hauptthread in diesem Beispiel jedoch keinen Synchronisationscode zum Ausführen hat, tritt er am Anfang in die Ereignisschleife ein. Beim Eintritt in die Ereignisschleife ist der Rückruf von setTimeout jedoch nicht unbedingt vollständig vorbereitet. Daher wird die Callback-Funktion setImmediate zuerst in der Phase check ausgeführt, und dann wird setTimeoutin den Timernausgeführt > Phase der nächsten Ereignisschleife. 🎜🎜Unter welchen Umständen hat die Rückruffunktion setImmediate für die gleiche Verzögerungszeit Vorrang vor der Rückruffunktion setTimeout? 🎜🎜Es ist eigentlich ganz einfach: Fügen Sie diese beiden einfach in die Phase Ausstehende Rückrufe, Leerlauf, Vorbereitung, Abfrage zwischen der Phase Timer und der Phase Prüfung ein Jede Phase ist in Ordnung. Denn nachdem diese Phasen ausgeführt wurden, werden sie auf jeden Fall mit der check-Phase und dann mit der timers-Phase fortfahren. 🎜🎜Wir nehmen die poll-Phase als Beispiel und schreiben diese beiden in die IO-Operation. 🎜rrreee🎜Wir führen es auch sieben Mal aus. Sie können sehen, dass setImmediate jedes Mal zuerst ausgeführt wird. 🎜🏜 Im Allgemeinen wird setTimeout bei gleicher Verzögerungszeit nicht zu 100 % vor setImmediate ausgeführt. 🎜

    先微任务再宏任务

    主线程同步代码执行完毕后,会先执行微任务再执行宏任务。

    我们来看下面的例子

    console.log("start");
    
    setTimeout(() => {
      console.log("setTimeout");
    });
    
    setImmediate(() => {
      console.log("setImmediate");
    });
    
    Promise.resolve().then(() => {
      console.log("Promise.resolve");
    });
    
    console.log("end");

    我们运行一下看结果,可以看到它是先执行了微任务然后再执行宏任务

    Vertiefendes Verständnis des Node-Event-Loop-Mechanismus (EventLoop).

    nextTick优于其它微任务

    在微任务中nextTick的优先级是最高的。

    我们来看下面的例子

    console.log("start");
    
    setTimeout(() => {
      console.log("setTimeout");
    });
    
    setImmediate(() => {
      console.log("setImmediate");
    });
    
    Promise.resolve().then(() => {
      console.log("Promise.resolve");
    });
    
    process.nextTick(() => {
      console.log("process.nextTick");
    });
    
    console.log("end");

    我们运行上面的代码,可以看到就算nextTick定义在resolve后面,它也是先执行的。

    Vertiefendes Verständnis des Node-Event-Loop-Mechanismus (EventLoop).

    微任务穿插在各个阶段间执行

    怎么理解这个穿插呢?其实就是在事件循环的六个阶段每个阶段执行完后会清空微任务队列。

    我们来看例子,我们建立了timers、check、poll三个阶段,并且每个阶段都产生了微任务。

    // timers阶段
    setTimeout(() => {
      console.log("setTimeout");
    
      Promise.resolve().then(() => {
        console.log("setTimeout Promise.resolve");
      });
    });
    
    // check阶段
    setImmediate(() => {
      console.log("setImmediate");
      Promise.resolve().then(() => {
        console.log("setImmediate Promise.resolve");
      });
    });
    
    // 微任务
    Promise.resolve().then(() => {
      console.log("Promise.resolve");
    });
    
    // 微任务
    process.nextTick(() => {
      console.log("process.nextTick");
      Promise.resolve().then(() => {
        console.log("nextTick Promise.resolve");
      });
    });

    我们来执行上面的代码

    Vertiefendes Verständnis des Node-Event-Loop-Mechanismus (EventLoop).

    可以看到,先执行微任务,再执行宏任务。先process.nextTick -> Promise.resolve。并且如果微任务继续产生微任务则会再次清空,所以就又输出了nextTick Promise.resolve

    接下来到timer阶段,输出setTimeout,并且产生了一个微任务,再进入到下个阶段前需要清空微任务队列,所以继续输出setTimeout Promise.resolve

    接下来到check阶段,输出setImmediate,并且产生了一个微任务,再进入到下个阶段前需要清空微任务队列,所以继续输出setImmediate Promise.resolve

    这也就印证了微任务会穿插在各个阶段之间运行。

    Vertiefendes Verständnis des Node-Event-Loop-Mechanismus (EventLoop).

    总结

    所以对于Node中的事件循环你只需要背好一以下几点就可以了

    • 当主线程同步代码执行完毕后才会进入事件循环

    • 事件循环总共分六个阶段,并且每个阶段都包括哪些回调需要记清楚。

    • 事件循环中会先执行微任务再执行宏任务。

    • 微任务会穿插在这六个阶段之间执行,每进入到下个阶段前会清空当前的微任务队列。

    • 微任务中process.nextTick的优先级最高,会优先执行。

    更多node相关知识,请访问:nodejs 教程

    Das obige ist der detaillierte Inhalt vonVertiefendes Verständnis des Node-Event-Loop-Mechanismus (EventLoop).. 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