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

Example Analysis of event Loop, timer and process.nextTick () in Node.js

2025-04-07 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

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

This article mainly introduces the example analysis of event loop, timer and process.nextTick () in Node.js, which is very detailed and has certain reference value. Interested friends must read it!

What is an event loop?

Although the JavaScript is single-threaded, the event loop allows the Node.js to perform non-blocking IGO operations by putting the operation into the system kernel as much as possible.

Because most modern kernels are multithreaded, they can handle multiple operations performed in the background. When one of the operations is completed, the kernel tells Node.js so that the corresponding callback can be added to the polling queue for final execution. We will explain in more detail later in this topic.

Event cycle interpretation

When Node.js starts, it initializes the event loop and processes the input script provided (or put into REPL, which is not covered in this document), which may make an asynchronous API call, schedule a timer, or call process.nextTick (), and then start processing the event loop.

The following figure shows a brief overview of the sequence of event loop operations.

┌──┐

┌─ > │ timers │

│ └─┬─┘

│ ┌─┴─┐

│ │ pending callbacks │

│ └─┬─┘

│ ┌─┴─┐

│ │ idle, prepare │

│ └─┬─┘ ┌─┐

│ ┌─┴─┐ │ incoming: │

│ │ poll │ {const delay = Date.now ()-timeoutScheduled; console.log (`$ {delay} ms have passed since I was Secreted`);}, 100); / / do someAsyncOperation which takes 95 ms to completesomeAsyncOperation (() = > {const startCallback = Date.now (); / / do something that will take 10ms. While (Date.now ()-startCallback

< 10) { // do nothing }}); 当事件循环进入轮询阶段时,它有一个空队列( fs.readFile() 尚未完成),因此它将等待剩余的ms数,直到达到最快的计时器阈值。 当它等待95毫秒传递时, fs.readFile() 完成读取文件,并且其完成需要10毫秒的回调被添加到轮询队列并执行。 当回调结束时,队列中不再有回调,因此事件循环将看到已达到最快定时器的阈值,然后回绕到定时器阶段以执行定时器的回调。 在此示例中,您将看到正在调度的计时器与正在执行的回调之间的总延迟将为105毫秒。 注意:为了防止轮询阶段使事件循环挨饿,libuv(实现Node.js事件循环的C库和平台的所有异步行为)在停止轮询之前也为事件提供了固定的最大值(取决于系统)。 等待回调(pending callbacks) 此阶段执行某些系统操作(例如TCP错误类型)的回调。 例如,如果TCP套接字在尝试连接时收到 ECONNREFUSED ,则某些*nix系统希望等待报告错误。 这将排队等待在等待回调阶段执行。 轮询(poll) 轮询阶段有两个主要功能: 1.计算它阻塞和轮询I / O的时间,然后 2.处理轮询队列中的事件。 当事件循环进入轮询阶段并且没有定时器调度时,将发生以下两种情况之一: 如果轮询队列不为空,则事件循环将遍历回调队列并且同步执行,直到队列已执行完或者达到系统相关的固定限制。 如果轮询队列为空,则会发生以下两种情况之一: setImmediate() setImmediate() 检查(check) 此阶段允许在轮询阶段完成后立即执行回调。 如果轮询阶段变为空闲并且脚本已使用 setImmediate() 排队,则事件循环可以继续到检查阶段而不是等待。 setImmediate() 实际上是一个特殊的计时器,它在事件循环的一个单独阶段运行。 它使用libuv API来调度在轮询阶段完成后执行的回调。 通常,在执行代码时,事件循环最终会到达轮询阶段,它将等待传入连接,请求等。但是,如果已使用 setImmediate() 调度回调并且轮询阶段变为空闲,则 将结束并继续检查阶段,而不是等待轮询事件。 关闭回调(close callbacks) 如果套接字或句柄突然关闭( 例如socket.destroy() ),则在此阶段将发出 'close' 事件。 否则它将通过 process.nextTick() 发出。 setImmediate() vs setTimeout() setImmediate 和 setTimeout() 类似,但根据它们的调用时间以不同的方式运行。 setImmediate() setTimeout() 执行定时器的顺序将根据调用它们的上下文而有所不同。 如果从主模块中调用两者,则时间将受到进程性能的限制(可能受到计算机上运行的其他应用程序的影响)。 例如,如果我们运行不在I / O周期内的以下脚本(即主模块),则执行两个定时器的顺序是不确定的,因为它受进程性能的约束: // timeout_vs_immediate.jssetTimeout(() =>

{console.log ('timeout');}, 0); setImmediate (() = > {console.log (' immediate');}); $node timeout_vs_immediate.jstimeoutimmediate$ node timeout_vs_immediate.jsimmediatetimeout

However, if you move two calls during the I / O cycle, the immediate callback is always performed first:

/ / timeout_vs_immediate.jsconst fs = require ('fs'); fs.readFile (_ _ filename, () = > {setTimeout () = > {console.log (' timeout');}, 0); setImmediate (() = > {console.log ('immediate');});}); $node timeout_vs_immediate.jsimmediatetimeout$ node timeout_vs_immediate.jsimmediatetimeout

The main advantage of using setImmediate () instead of setTimeout () is that setImmediate () will always execute before any timer (if scheduled during the I / O cycle), regardless of how many timers exist.

Process.nextTick ()

Understand process.nextTick ()

You may have noticed that process.nextTick () is not shown in the figure, even if it is part of an asynchronous API. This is because process.nextTick () is not technically part of the event loop. Instead, nextTickQueue will post-process after the current operation completes, regardless of the current stage of the event loop.

Looking back at our diagram, whenever process.nextTick () is called at a given stage, all callbacks passed to process.nextTick () will be resolved before the event loop continues. This can lead to some bad situations because it allows you to "starve" your I / O through a recursive call to process.nextTick (), which prevents the event loop from reaching the polling phase.

Why are you allowed?

Why is such a thing included in Node.js? Part of this is a design concept in which API should always be asynchronous, even if it is not necessary. Take this code snippet as an example:

Function apiCall (arg, callback) {if (typeof arg! = = 'string') return process.nextTick (callback, new TypeError (' argument should be string'));}

This code does parameter checking and, if incorrect, passes the error to the callback. The recently updated API allows parameters to be passed to process.nextTick (), allowing it to propagate any parameters passed after the callback as parameters to the callback, so you don't have to nest functions.

What we are doing is passing the error back to the user, but only after we allow the rest of the user code to execute. By using process.nextTick (), we ensure that apiCall () always runs its callback after the rest of the user code and before allowing the event loop to continue. To achieve this, allow the JS call stack to expand and then immediately execute the provided callback, which allows one to make a recursive call to process.nextTick () without reaching RangeError: exceeds the maximum call stack size of v8.

This concept may lead to some potential problems. Take this clip as an example:

Let bar;// this has an asynchronous signature, but calls callback synchronouslyfunction someAsyncApiCall (callback) {callback ();} / the callback is called before `someAsyncApiCall` completes.someAsyncApiCall (() = > {/ / since someAsyncApiCall has completed, bar hasn't been assigned any value console.log ('bar', bar); / / undefined}); bar = 1

The user defines someAsyncApiCall () as having an asynchronous signature, but it is actually synchronous. When it is called, the callback provided to someAsyncApiCall () is invoked at the same stage of the event loop, because someAsyncApiCall () does not actually do anything asynchronously. Therefore, the callback attempts to reference bar, even though it may not have the variable in scope, because the script cannot be run.

By placing the callback in process.nextTick (), the script still runs, allowing all variables, functions, and so on to be initialized before the callback is called. It also has the advantage of not allowing the event loop to continue. It may be useful to warn the user of an error before allowing the event loop to continue. The following is the previous example using process.nextTick ():

Let bar;function someAsyncApiCall (callback) {process.nextTick (callback);} someAsyncApiCall (() = > {console.log ('bar', bar); / / 1}); bar = 1

This is another real-world example:

Const server = net.createServer (() = > {}). Server.on (8080); server.on ('listening', () = > {})

When only the port is passed, the port is bound immediately. Therefore, the 'listening' callback can be called immediately. The problem is that the .on ('listening') callback will not be set at that time.

To solve this problem, the 'listening' event is queued in nextTick () to allow the script to run. This allows users to set up any event handlers they want.

Process.nextTick () vs setImmediate ()

As far as the user is concerned, we have two similar calls, but their names are confusing.

Process.nextTick () setImmediate ()

In essence, names should be exchanged. Process.nextTick () triggers faster than setImmediate (), but this is created in the past and is unlikely to change. Making this switch destroys most of the packages on npm. More new modules are added every day, which means we are waiting every day for more potential damage. Although they are confusing, their own name will not change.

We recommend that developers use setImmediate () in all cases because it is easier to reason (and it leads to code compatibility with broader environments such as browser JS. )

Why use process.nextTick ()?

There are two main reasons:

Allow the user to handle errors, clear any unneeded resources, or try the request again before the event loop continues.

Sometimes you need to allow callbacks to run after the call stack is expanded but before the event loop continues.

One example is to match the user's expectations. A simple example:

Const server = net.createServer (); server.on ('connection', (conn) = > {}); server.listen (8080); server.on (' listening', () = > {})

Suppose listen () runs at the beginning of the event loop, but the listening callback is placed in setImmediate (). Unless the hostname is passed, it will be bound to the port immediately. For the event loop to continue, it must reach the polling phase, which means that the non-zero probability of the connection that may have been received allows the connection event to be triggered before listening for the event.

Another example is to run a function constructor, such as inheriting from EventEmitter, which wants to call an event in the constructor:

Const EventEmitter = require ('events'); const util = require (' util'); function MyEmitter () {EventEmitter.call (this); this.emit ('event');} util.inherits (MyEmitter, EventEmitter); const myEmitter = new MyEmitter (); myEmitter.on (' event', () = > {console.log ('an event occurredness');})

You cannot immediately issue an event from the constructor because the script will not process to the location where the user assigned a callback to the event. Therefore, in the constructor itself, you can use process.nextTick () to set the callback to emit an event after the constructor completes, providing the desired results:

Const EventEmitter = require ('events'); const util = require (' util'); function MyEmitter () {EventEmitter.call (this); / / use nextTick to emit the event once a handler is assigned process.nextTick () = > {this.emit ('event');});} util.inherits (MyEmitter, EventEmitter); const myEmitter = new MyEmitter (); myEmitter.on (' event', () = > {console.log ('an event occurredholders');}) The above is all the content of the article "sample Analysis of event Loops, timers and process.nextTick () in Node.js". 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