Die Ereignisschleife ist ein grundlegender Bestandteil von Node.js. Sie ermöglicht die asynchrone Programmierung, indem sie sicherstellt, dass der Hauptthread nicht blockiert wird. Das Verständnis der Ereignisschleife ist für die Erstellung effizienter Anwendungen von entscheidender Bedeutung. Der folgende Artikel wird Ihnen ein detailliertes Verständnis der Ereignisschleife in Node vermitteln. Ich hoffe, er wird Ihnen hilfreich sein!
Sie verwenden Node.js schon seit einiger Zeit, haben einige Apps erstellt, verschiedene Module ausprobiert und fühlen sich sogar mit der asynchronen Programmierung vertraut. Aber etwas stört Sie immer wieder – die Ereignisschleife.
Wenn Sie wie ich sind, haben Sie unzählige Stunden damit verbracht, Dokumentationen zu lesen und Videos anzuschauen, um die Ereignisschleife zu verstehen. Aber selbst als erfahrener Entwickler kann es sein, dass Sie Schwierigkeiten haben, die Funktionsweise vollständig zu verstehen. Aus diesem Grund habe ich diesen visuellen Leitfaden vorbereitet, um Ihnen zu helfen, die Node.js-Ereignisschleife vollständig zu verstehen. Lehnen Sie sich also zurück, trinken Sie eine Tasse Kaffee und tauchen Sie ein in die Welt der Node.js-Ereignisschleife. [Verwandte Tutorial-Empfehlungen: nodejs-Video-Tutorial, Programmierunterricht]
Wir beginnen mit einem Überblick über die asynchrone Programmierung in JavaScript. Während JavaScript in Web-, Mobil- und Desktop-Anwendungen verwendet wird, ist es wichtig zu bedenken, dass JavaScript im Kern eine synchrone, blockierende Single-Thread-Sprache ist. Lassen Sie uns diesen Satz anhand eines kurzen Codeausschnitts verstehen.
// index.js function A() { console.log("A"); } function B() { console.log("B"); } A() B() // Logs A and then B
Wenn wir zwei Funktionen haben, die Nachrichten in der Konsole protokollieren, wird der Code von oben nach unten Zeile für Zeile ausgeführt. Im obigen Codeausschnitt sehen wir, dass A vor B aufgezeichnet wird.
JavaScript blockiert aufgrund seiner synchronen Natur. Unabhängig davon, wie lange der vorherige Prozess dauert, werden nachfolgende Prozesse erst gestartet, wenn der erstere abgeschlossen ist. Wenn Funktion A im Codeausschnitt einen großen Codeblock ausführen muss, muss JavaScript diesen Vorgang abschließen, ohne zu Funktion B zu verzweigen. Auch wenn dieser Code 10 Sekunden oder sogar eine Minute dauert.
Möglicherweise ist Ihnen diese Situation in Ihrem Browser schon einmal begegnet. Wenn eine Webanwendung im Browser ausgeführt wird und einige intensive Codeblöcke ausführt, ohne die Kontrolle an den Browser zurückzugeben, kann es sein, dass der Browser einfriert, was als Blockierung bezeichnet wird. Der Browser wird daran gehindert, weiterhin Benutzereingaben zu verarbeiten und andere Aufgaben auszuführen, bis die Webanwendung die Prozessorsteuerung an den Browser zurückgibt.
Ein Thread ist der Prozess, den Ihr JavaScript-Programm zum Ausführen von Aufgaben verwenden kann. Jeder Thread kann jeweils nur eine Aufgabe ausführen. Im Gegensatz zu anderen Sprachen, die Multithreading unterstützen und mehrere Aufgaben gleichzeitig ausführen können, verfügt JavaScript nur über einen Thread, den sogenannten Hauptthread, der Code ausführt.
Wie Sie sich vorstellen können, verursacht dieses JavaScript-Modell Probleme, da wir auf den Abruf der Daten warten müssen, bevor wir mit der Ausführung des Codes fortfahren können. Diese Wartezeit kann mehrere Sekunden dauern, in denen wir keinen anderen Code ausführen können. Wenn JavaScript die Verarbeitung fortsetzt, ohne zu warten, tritt ein Fehler auf. Wir müssen asynchrones Verhalten in JavaScript implementieren. Gehen wir zu Node.js und werfen einen Blick darauf.
Die Node.js Runtime ist eine Umgebung, die es Ihnen ermöglicht, JavaScript-Programme ohne Verwendung eines Browsers zu verwenden und auszuführen. Core – die Node-Laufzeit, besteht aus drei Hauptkomponenten.
Während alle Teile wichtig sind, ist libuv die Schlüsselkomponente der asynchronen Programmierung in Node.js.
Libuv ist eine plattformübergreifende Open-Source-Bibliothek, die in der Sprache C geschrieben ist. In der Node.js-Laufzeit besteht seine Rolle darin, Unterstützung für die Verarbeitung asynchroner Vorgänge bereitzustellen. Werfen wir einen Blick darauf, wie es funktioniert.
让我们来概括一下代码在 Node 运行时中的执行方式。在执行代码时,位于图片左侧的 V8 引擎负责 JavaScript 代码的执行。该引擎包含一个内存堆(Memory heap)和一个调用栈(Call stack)。
每当声明变量或函数时,都会在堆上分配内存。执行代码时,函数就会被推入调用栈中。当函数返回时,它就从调用栈中弹出了。这是对栈数据结构的简单实现,最后添加的项是第一个被移除。在图片右侧,是负责处理异步方法的 libuv。
每当我们执行异步方法时,libuv 接管任务的执行。然后使用操作系统本地异步机制运行任务。如果本地机制不可用或不足,则利用其线程池来运行任务,并确保主线程不被阻塞。
首先,让我们来看一下同步代码执行。以下代码由三个控制台日志语句组成,依次记录“First”,“Second”和“Third”。我们按照运行时执行顺序来查看代码。
// index.js console.log("First"); console.log("Second"); console.log("Third");
以下是 Node 运行时执行同步代码的可视化展示。
执行的主线程始终从全局作用域开始。全局函数(如果我们可以这样称呼它)被推入堆栈中。然后,在第 1 行,我们有一个控制台日志语句。这个函数被推入堆栈中。假设这个发生在 1 毫秒时,“First” 被记录在控制台上。然后,这个函数从堆栈中弹出。
执行到第 2 行时。假设到第 2 毫秒了,log 函数再次被推入堆栈中。“Second”被记录在控制台上,并弹出该函数。
最后,执行到第 3 行了。第 3 毫秒时,log 函数被推入堆栈,“Third”将记录在控制台上,并弹出该函数。此时已经没有代码要执行,全局也被弹出。
接下来,让我们看一下异步代码执行。有以下代码片段:包含三个日志语句,但这次第二个日志语句传递给了fs.readFile()
作为回调函数。
执行的主线程始终从全局作用域开始。全局函数被推入堆栈。然后执行到第 1 行,在第 1 毫秒时,“First”被记录在控制台中,并弹出该函数。然后执行移动到第 2 行,在第 2毫秒时,readFile
方法被推入堆栈。由于 readFile
是异步操作,因此它会转移(off-loaded)到 libuv。
JavaScript 从调用堆栈中弹出了 readFile
方法,因为就第 2 行的执行而言,它的工作已经完成了。在后台,libuv 开始在单独的线程上读取文件内容。在第 3 毫秒时,JavaScript 继续进行到第 5 行,将 log 函数推入堆栈,“Third”被记录到控制台中,并将该函数弹出堆栈。
大约在第 4 毫秒左右,假设文件读取任务已经完成,则相关回调函数现在会在调用栈上执行, 在回调函数内部遇到 log 函数。
log 函数推入到到调用栈,“Second”被记录到控制台并弹出 log 函数 。由于回调函数中没有更多要执行的语句,因此也被弹出 。没有更多代码可运行了 ,所以全局函数也从堆栈中删除 。
控制台输出“First”,“Third”,然后是“Second”。
很明显,libuv 用于处理 Node.js 中的异步操作。对于像处理网络请求这样的异步操作,libuv 依赖于操作系统原生机制。对于没有本地 OS 支持的异步读取文件的操作,libuv 则依赖其线程池以确保主线程不被阻塞。然而,这也引发了一些问题。
setTimeout
和 setInterval
这类延迟执行回调函数的方法又是何时执行回调函数呢?setTimeout
和 readFile
这类异步任务同时完成,Node 如何决定哪个回调函数先在调用栈上运行?其中一个会有更多的优先级吗?所有这些问题都可以通过理解 libuv 核心部分——事件循环来得到答案。
Technisch gesehen ist die Ereignisschleife nur ein C-Sprachprogramm. Aber in Node.js können Sie es sich als Entwurfsmuster zum Koordinieren der Ausführung von synchronem und asynchronem Code vorstellen.
Die Ereignisschleife ist eine Schleife, die so lange läuft, wie Ihre Node.js-Anwendung ausgeführt wird. In jeder Schleife gibt es sechs verschiedene Warteschlangen, die jeweils eine oder mehrere Rückruffunktionen enthalten, die schließlich auf dem Aufrufstapel ausgeführt werden müssen.
setTimeout
und setInterval
gespeichert wird. setTimeout
和 setInterval
相关的回调函数。fs
和 http
模块中提供的相关方法。setImmediate
函数相关的回调函数,这是特定于Node 的功能。最后,有两个不同队列组成微任务队列(microtask queue)。
process.nextTick
函数关联的回调函数。Promise
相关联的回调函数。需要注意的是计时器、I/O、检查和关闭队列都属于 libuv。然而,两个微任务队列并不属于 libuv。尽管如此,它们仍然是 Node 运行时环境中扮演着重要角色,并且在执行回调顺序方面发挥着重要作用。说到这里, 让我们来理解一下事件循环是如何工作的。
图中箭头是一个提示,但可能还不太容易理解。让我来解释一下队列的优先级顺序。首先要知道,所有用户编写的同步 JavaScript 代码都比异步代码优先级更高。这表示只有在调用堆栈为空时,事件循环才会发挥作用。
在事件循环中,执行顺序遵循某些规则。需要掌握的规则还是有一些的,我们逐个的了解一下:
此时,如果还有更多的回调需要处理,那么事件循环再运行一次(译注:事件循环在程序运行期间一直在运行,在当前没有可供处理的任务情况下,会处于等待状态,一旦有新任务就会执行),并重复相同的步骤。另一方面,如果所有回调都已执行并且没有更多代码要处理(译注:也就是程序执行结束),则事件循环退出。
这就是 libuv 事件循环在 Node.js 中执行异步代码的作用。有了这些规则,我们可以重新审视之前提出的问题。
当一个异步任务在 libuv 中完成时,什么时候 Node 会在调用栈上运行相关联的回调函数?
答案:只有当调用栈为空时才执行回调函数。
Node 是否会等待调用栈为空后再运行回调函数?还是打断正常执行流来运行回调函数?
答案:运行回调函数时不会打断正常执行流。
像 setTimeout
和 setInterval
Zweitens gibt es eine E/A-Warteschlange, die Rückruffunktionen für alle asynchronen Methoden enthält, z. B. fs
- und http
-Module. Verwandte Methoden, die in bereitgestellt werden. Die dritte ist die Prüfwarteschlange, die die Rückruffunktion im Zusammenhang mit der Funktion setImmediate
enthält, bei der es sich um eine knotenspezifische Funktion handelt.
process.nextTick
verknüpft ist. 🎜Die Promise-Warteschlange speichert die Rückruffunktionen, die mit lokalem Promise
in JavaScript verknüpft sind. 🎜Es ist zu beachten, dass Timer, E/A, Prüf- und Schließwarteschlangen alle zu libuv gehören. Die beiden Mikrotask-Warteschlangen gehören jedoch nicht zu libuv. Dennoch spielen sie immer noch eine wichtige Rolle in der Node-Laufzeitumgebung und spielen eine wichtige Rolle bei der Reihenfolge, in der Rückrufe ausgeführt werden. Lassen Sie uns jedoch verstehen, wie die Ereignisschleife funktioniert. 🎜setTimeout
und setInterval
, die die Ausführung von Rückruffunktionen verzögern. Wann wird die Rückruffunktion ausgeführt? 🎜🎜Antwort: setTimeout
und setInterval
werden mit der ersten Priorität unter allen Rückruffunktionen ausgeführt (unabhängig von der Mikrotask-Warteschlange). setTimeout
和 setInterval
的所有回调函数中第一优先级执行的(不考虑微任务队列)。
如果两个异步任务(例如 setTimeout
和 readFile
Wenn zwei asynchrone Aufgaben (z. B. setTimeout
und readFile
) gleichzeitig abgeschlossen werden, wie entscheidet Node, welche Rückruffunktion zuerst im Aufrufstapel ausgeführt wird? Hat einer eine höhere Priorität als der andere?
Wir haben bisher viel gelernt, aber ich hoffe, Sie können sich die im Bild unten gezeigte Ausführungssequenz merken, denn sie zeigt vollständig, wie Node.js asynchronen Code hinter den Kulissen ausführt.
Fazit
Weitere Informationen zu Knoten finden Sie unter: 🎜nodejs-Tutorial🎜! 🎜Das obige ist der detaillierte Inhalt vonLassen Sie uns über die Ereignisschleife in Node sprechen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!