Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to implement event Loop in Node

2025-02-25 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

This article mainly introduces how to achieve the event cycle in Node, the article introduces in great detail, has a certain reference value, interested friends must read it!

Node.js is a single-threaded language that handles non-blocking Iripple O operations through event loops.

When Node.js runs as the server of JavaScript, it mainly deals with the network and files, without the rendering phase of the event cycle in the browser.

There is a HTML specification in the browser to define the processing model of the event loop, which is then implemented by various browser vendors. The definition and implementation of event loops in Node.js come from Libuv.

Libuv is designed around the event-driven asynchronous Ithumb O model, originally written for Node.js, and provides a cross-platform support library. The following figure shows its components. Network I Pot O is the part related to network processing, there are file operations and DNS on the right, and the bottom epoll, kqueue, event ports and IOCP are the implementations of different underlying operating systems.

The six stages of the event cycle

When Node.js starts, it initializes the event loop, processes the provided script, and executes the synchronous code directly on the stack. Asynchronous tasks (network requests, file operations, timers, etc.) transfer operations to the background after calling the API transfer callback function and are handled by the system kernel. At present, most kernels are multithreaded, and when one of these operations is completed, the kernel tells Node.js to add a callback function to the polling queue and wait for the opportunity to execute.

On the left side of the figure is the description of the event cycle process on the official website of Node.js, and on the right is the description of Node.js on the official website of Libuv, which is an introduction to the event cycle. Not everyone can read the source code. These two documents are usually more direct learning reference documents for the event cycle, and are also introduced in detail on the Node.js official website, which can be used as a reference.

The event cycle shown on the Node.js official website on the left is divided into six phases, each of which has a FIFO (first-in, first-out) queue to execute the callback function. The priority order between these phases is still clear.

As described in more detail on the right, before the iteration of the event loop, it is necessary to determine whether the loop is active (there are waiting asynchronous Ipool O, timer, etc.). If it is active, the iteration starts, otherwise the loop will exit immediately.

Each stage is discussed separately below.

Timers (timer phase)

First, the event loops into the timer phase, which consists of two API setTimeout (cb, ms) and setInterval (cb, ms). The first one is executed only once, and the second one is repeated.

This phase checks whether there is an expired timer function, and if so, executes the expired timer callback function, as in the browser, the delay time passed in the timer function is always later than we expected. it is affected by the operating system or other running callback functions.

For example, in the following example, we set a timer function that is expected to be executed in 1000 milliseconds.

Const now = Date.now (); setTimeout (function timer1 () {log (`delay ${Date.now ()-now} ms`);}, 1000); setTimeout (function timer2 () {log (`delay ${Date.now ()-now} ms`);}, 5000); someOperation (); function someOperation () {/ / sync operation... While (Date.now ()-now

< 3000) {}} 当调用 setTimeout 异步函数后,程序紧接着执行了 someOperation() 函数,中间有些耗时操作大约消耗 3000ms,当完成这些同步操作后,进入一次事件循环,首先检查定时器阶段是否有到期的任务,定时器的脚本是按照 delay 时间升序存储在堆内存中,首先取出超时时间最小的定时器函数做检查,如果 nowTime - timerTaskRegisterTime >

Delay takes out the callback function for execution, otherwise it continues to check and move on to the next phase when it reaches a timer function that does not expire or reaches the maximum number of system dependencies.

In our example, assume that the current time to finish executing the someOperation () function is T + 3000:

Check the timer1 function. The current time is T + 3000-T > 1000, which has exceeded the expected delay time. Take out the callback function and continue the check.

Check the timer2 function, the current time is T + 3000-T

< 5000,还没达到预期的延迟时间,此时退出定时器阶段。 pending callbacks 定时器阶段完成后,事件循环进入到 pending callbacks 阶段,在这个阶段执行上一轮事件循环遗留的 I/O 回调。根据 Libuv 文档的描述:大多数情况下,在轮询 I/O 后立即调用所有 I/O 回调,但是,某些情况下,调用此类回调会推迟到下一次循环迭代。听完更像是上一个阶段的遗留。 idle, prepare idle, prepare 阶段是给系统内部使用,idle 这个名字很迷惑,尽管叫空闲,但是在每次的事件循环中都会被调用,当它们处于活动状态时。这一块的资料介绍也不是很多。略... poll poll 是一个重要的阶段,这里有一个概念观察者,有文件 I/O 观察者,网络 I/O 观察者等,它会观察是否有新的请求进入,包含读取文件等待响应,等待新的 socket 请求,这个阶段在某些情况下是会阻塞的。 阻塞 I/O 超时时间 在阻塞 I/O 之前,要计算它应该阻塞多长时间,参考 Libuv 文档上的一些描述,以下这些是它计算超时时间的规则: 如果循环使用 UV_RUN_NOWAIT 标志运行、超时为 0。 如果循环将要停止(uv_stop() 被调用),超时为 0。 如果没有活动的 handlers 或 request,超时为 0。 如果有任何 idle handlers 处于活动状态,超时为 0。 如果有任何待关闭的 handlers,超时为 0。 如果以上情况都没有,则采用最近定时器的超时时间,或者如果没有活动的定时器,则超时时间为无穷大,poll 阶段会一直阻塞下去。 示例一 很简单的一段代码,我们启动一个 Server,现在事件循环的其它阶段没有要处理的任务,它会在这里等待下去,直到有新的请求进来。 const http = require('http');const server = http.createServer();server.on('request', req =>

{console.log (req.url);}) server.listen (3000); example 2

Combined with the timer of Phase 1, looking at an example, first start app.js as the server to simulate the delayed 3000ms response, this is just to cooperate with the test. Then run client.js to see the execution of the event loop:

First the program calls a timer that times out after 1000ms.

Then call the asynchronous function someAsyncOperation () to read the data from the network, and we assume that this asynchronous network read requires 3000ms.

When the event loop starts, it first enters the timer phase, finds that there is no timer function that times out, and continues to execute downward.

Pass through pending callbacks-> idle,prepare when entering the poll phase, the http.get () is not yet completed and its queue is empty. Referring to the above poll blocking timeout rule, the event loop mechanism will check the timer that reaches the threshold as fast as possible instead of waiting here all the time.

After approximately passing the 1000ms, enter the next event loop to enter the timer, execute the timer callback function that expires, and we will see the log setTimeout run after 1003 ms.

After the timer phase ends, it enters the poll phase again and continues to wait.

/ / client.jsconst now = Date.now (); setTimeout () = > log (`setTimeout run after ${Date.now ()-now} ms`), 1000); someAsyncOperation (); function someAsyncOperation () {http.get ('log () = > {log (`fetch data success after ${Date.now ()-now} ms`);});} / / app.jsconst http = require (' http') Http.createServer ((req, res) = > {setTimeout (()) = > {res.end ('OKTV')}, 3000);}) .subscription (3000)

When the poll phase queue is empty and the script is scheduled by setImmediate (), the event loop also ends the poll phase and moves on to the next phase, check.

Check

The check phase runs after the poll phase, which contains an API setImmediate (cb) if there is a callback function triggered by setImmediate, it is fetched and executed until the queue is empty or the maximum limit of the system is reached.

SetTimeout VS setImmediate

Compared with setTimeout and setImmediate, this is a common example, based on the timing of the call and the timer may be affected by other running applications on the computer, their output order is not always fixed.

SetTimeout (() = > log ('setTimeout')); setImmediate (() = > log (' setImmediate')); / / first run setTimeoutsetImmediate// and second run setImmediatesetTimeoutsetTimeout VS setImmediate VS fs.readFile

But once these two functions are called in an I _ setImmediate O loop, the call will always take precedence. Because setImmediate belongs to the check phase, it is always run after the end of the poll phase in the event loop, and this order is determined.

Fs.readFile (_ _ filename, () = > {setTimeout () = > log ('setTimeout')); setImmediate (() = > log (' setImmediate'));}) close callbacks

In Libuv, if you call the close handle uv_close (), it invokes the close callback, which is close callbacks, the last phase of the event loop.

The work at this stage is more like doing some cleanup work. For example, when socket.destroy () is called, the 'close' event will be issued at this stage. After executing the callback function in the queue at this stage, the event loop checks to see if the loop returns alive, and if it exits for no, the next new event loop will continue.

Event loop flowchart with Microtask

In the event cycle of the browser, the task is divided into Task and Microtask, which is divided into stages in Node.js. Above, we introduce the six stages of the Node.js event cycle, which are mainly used by users in four stages: timer, poll, check and close callback, and the remaining two are scheduled by the system. The tasks generated by these phases can be regarded as the source of Task tasks, which is often called "Macrotask macro tasks".

Usually when we talk about an event loop, we also include Promise in Microtask,Node.js, and perhaps a little-noticed function queueMicrotask, which is implemented after Node.js v11.0.0, see PR/22951.

After each phase of execution, the event loop in Node.js checks whether there are tasks waiting to be executed in the micro-task queue.

Differences before and after Node.js 11.x

Node.js before and after v11.x, if there are both executable Task and Microtask in each stage, there will be some differences. First, take a look at a code:

SetImmediate (() = > {log ('setImmediate1'); Promise.resolve (' Promise microtask 1'). Then (log);}); setImmediate () = > {log ('setImmediate2'); Promise.resolve (' Promise microtask 2'). Then (log);})

Prior to Node.js v11.x, if there were multiple executable Task in the current phase, the execution was finished before the execution of the micro task. The results of running based on v10.22.1 are as follows:

SetImmediate1setImmediate2Promise microtask 1Promise microtask 2

After Node.js v11.x, if there are multiple executable Task in the current stage, first take out one Task for execution, empty the corresponding micro-task queue, and then take out the next executable task again to continue execution. The results of running based on v14.15.0 are as follows:

SetImmediate1Promise microtask 1setImmediate2Promise microtask 2

This execution order issue before Node.js v11.x is considered to be a Bug that should be fixed after v11.x and modified its execution timing, consistent with the browser, see the issues/22257 discussion for details.

Special process.nextTick ()

There is also an asynchronous function process.nextTick () in Node.js, which is not technically part of the event loop, but is processed after the current operation is completed. If there is a recursive process.nextTick () call, this will be very bad, it will block the event loop.

As shown in the following example, an example of a recursive call to process.nextTick () is shown. The event loop is currently inside the I _ process.nextTick O loop. When the synchronization code is finished, process.nextTick () is executed immediately, and it falls into an infinite loop. Unlike synchronous recursion, it does not touch the v8 maximum call stack limit. But it destroys the event round robin, and setTimeout will never be executed.

Fs.readFile (_ _ filename, () = > {process.nextTick () = > {log ('nextTick'); run (); function run () {process.nextTick (() = > run ());}}); log (' sync run'); setTimeout (() = > log ('setTimeout'));}); / / output sync runnextTick

Changing process.nextTick to setImmediate is recursive, but it does not affect event loop scheduling, and setTimeout is executed in the next event loop.

Fs.readFile (_ _ filename, () = > {process.nextTick () = > {log ('nextTick'); run (); function run () {setImmediate (() = > run ());}}); log (' sync run'); setTimeout (() = > log ('setTimeout'));}); / / output sync runnextTicksetTimeout

Process.nextTick is executed immediately, and setImmediate is executed during the check phase of the next event loop. However, their names are really confusing, and it may be better to exchange the two names, but it is a legacy and is unlikely to change, as it destroys most of the software packages on NPM.

Developers are also advised to use setImmediate () as much as possible in the Node.js documentation, which is easier to understand.

The above is all the content of the article "how to implement the event cycle in Node". Thank you for reading! Hope to share the content to help you, more related knowledge, welcome to follow the industry information channel!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report