JavaScript事件循环与Node JS事件循环

Event Loop is a confusing enough topic to newcomers and is not often 100% understood. What makes this even confusing is, there are two buzzwords called “NodeJS Event Loop” and “JavaScript Event Loop” where the latter speaks about the event loop in the browser. This distinction leads to questions such as:

对于新手来说,事件循环是一个令人困惑的话题,通常不会100%被理解。 令人感到困惑的是,有两个流行词叫做“ NodeJS Event Loop”和“ JavaScript Event Loop”,后者在浏览器中谈论事件循环。 这种区别导致了以下问题:

  • Are these two the same, similar in behaviour, or different completely?

    这两个是相同的,行为相似的还是完全不同的?
  • And if different, what are the differences?

    如果有区别,有什么区别?
  • If they are the same, why do we have disambiguation of “NodeJS event loop” and “JavaScript Event Loop” in the first place?

    如果它们相同,那么为什么我们首先要消除“ NodeJS事件循环”和“ JavaScript事件循环”的歧义?

In short, Yes, they are similar in certain ways. And also, Yes, they are also different in certain implementation aspects. Therefore, In this article, I’m going to discuss this disambiguation using a couple of examples to clarify some of the burning questions you may have regarding the topic.

简而言之, 是的,它们在某些方面相似 。 而且, 是的,它们在某些实现方面也有所不同 。 因此,在本文中,我将使用几个示例来讨论这种歧义消除,以阐明您可能对该主题存在的一些亟待解决的问题。

This article is part of my event loop article series, which I’ve been writing throughout the last few years (yeah, a very slow journey). In case you’d like to learn more specifically about the NodeJS Event Loop or refresh your knowledge, feel free to check out the rest of the articles in the series listed below:

本文是我的事件循环文章系列的一部分,这是我在过去几年中一直写的内容( 是的,非常缓慢 )。 如果您想更具体地了解NodeJS事件循环或更新知识,请随时阅读以下系列文章中的其余文章:

什么是“事件循环”? (What exactly is an “Event Loop”?)

The term “Event Loop” is a generic programming pattern. It describes a simple loop which iterates through the results of completed events, and process them. JavaScript/NodeJS event loops are no different.

术语“事件循环”是一种通用的编程模式。 它描述了一个简单的循环,该循环遍历已完成事件的结果并进行处理。 JavaScript / NodeJS事件循环没有什么不同。

When JavaScript applications run, they fire various events, and these events will cause corresponding event handlers to be enqueued for processing. The event loop continuously watches for any queued event handlers and will process them accordingly.

当JavaScript应用程序运行时,它们会触发各种事件,这些事件将导致相应的事件处理程序进入队列进行处理。 事件循环持续监视所有排队的事件处理程序,并将对其进行相应处理。

An infinite Cat Loop!
无限的猫循环!

“事件循环”-根据HTML5规范 (“The Event Loop” — according to HTML5 Spec)

HTML5 Spec describes a standard set of guidelines vendors could use to develop browsers/JavaScript runtimes or other associated libraries. It also describes a set of guidelines to implement the event loop model, and other JavaScript features which might be associated with the event loop, such as Timers.

HTML5规范描述了供厂商用来开发浏览器/ JavaScript运行时或其他相关库的一组标准准则。 它还描述了一组实现事件循环模型准则 ,以及可能与事件循环相关的其他JavaScript功能,例如Timers

Most of the browsers and JS runtimes tend to follow these guidelines as-is for better compatibility across the world-wide-web. However, there are situations where they slightly deviate from this single source of truth, causing interesting (and sometimes annoying) consequences.

大多数浏览器和JS运行时都倾向于按原样遵循这些准则,以便在整个万维网上实现更好的兼容性。 但是,在某些情况下,它们会稍微偏离这一单一的真理来源,从而导致有趣的(有时是令人讨厌的)后果。

In this article, I’ll discuss a few such cases, especially focusing on NodeJS vs Browser. I may not dig deep into individual browser implementation details because they are prone to change at any time.

在本文中,我将讨论一些此类情况,尤其是NodeJS vs Browser。 我可能不会深入研究各个浏览器的实现细节,因为它们随时可能更改。

客户端与服务器端JavaScript (Client-side vs Server-Side JavaScript)

For years, JavaScript has been limited to client-side applications such as interactive web applications run on the browser. Using NodeJS, JavaScript can be used to develop server-side applications as well. Though it’s the same programming language which is used in both use cases, client-side and server-side have different requirements.

多年来,JavaScript一直局限于客户端应用程序,例如在浏览器上运行的交互式Web应用程序。 使用NodeJS,JavaScript还可用于开发服务器端应用程序。 尽管这两种用例都使用相同的编程语言,但是客户端和服务器端有不同的要求。

The browser is a sandboxed environment and JavaScript in the browser doesn’t have the freedom to perform certain tasks server-side JavaScript can do, such as file system operations, certain network operations etc. This requires event loops in server-side JavaScript (NodeJS) to be able to cater to these additional requirements.

浏览器是沙盒环境,浏览器中JavaScript不能自由执行服务器端JavaScript可以执行的某些任务,例如文件系统操作,某些网络操作等。这需要服务器端JavaScript(NodeJS)中的事件循环),以便能够满足这些附加要求。

Both the browser and NodeJS implements an asynchronous event-driven pattern with JavaScript. However, the “Events”, in a browser’s context, are user interactions on web pages (e.g, clicks, mouse movements, keyboard events etc.), but in Node’s context, events are asynchronous server-side operations (e.g, File I/O access, Network I/O etc.). Due to this difference of needs, Chrome and Node have different Event Loop implementations, though they share the same V8 JavaScript engine to run JavaScript.

浏览器和NodeJS都使用JavaScript实现了异步事件驱动模式。 但是,在浏览器的上下文中,“事件”是网页上的用户交互(例如,单击,鼠标移动,键盘事件等),而在Node的上下文中,“事件”是服务器端的异步操作(例如,文件I / O访问,网络I / O等)。 由于需求的差异,Chrome和Node具有不同的事件循环实现,尽管它们共享相同的V8 JavaScript引擎来运行JavaScript。

Since “the event loop” is nothing but a programming pattern, V8 allows the ability to plug-in an external event loop implementation to work with its JavaScript runtime. Using this flexibility, the Chrome browser uses libevent as its event loop implementation, and NodeJS uses libuv to implement the event loop. Therefore, chrome’s event loop and NodeJS’s event loop are based on two different libraries and which have differences, but they also share the similarities of the common “Event Loop” programming pattern.

由于“事件循环”仅是一种编程模式,因此V8允许插入外部事件循环实现以与其JavaScript运行时一起工作。 利用这种灵活性,Chrome浏览器使用libevent作为其事件循环实现,而NodeJS使用libuv 实现事件循环。 因此,chrome的事件循环和NodeJS的事件循环基于两个不同的库,并且具有不同之处,但是它们也共享常见的“事件循环”编程模式的相似之处。

浏览器与节点—有何不同? (Browsers vs Node — What’s different?)

微任务与宏任务的区别 (Micro-tasks vs Macro-tasks differences)

What are microtasks and macrotasks? In short, Macro-tasks and Micro-tasks are two types of asynchronous tasks. However, Micro-tasks have a higher priority compared to Macro-tasks. An example of a micro-task is a promise callback. And a setTimeout callback is an example of a macro-task.

什么是微任务和宏任务? 简而言之,宏任务和微任务是两种异步任务。 但是,与宏任务相比,微任务具有更高的优先级。 微任务的一个示例是promise回调。 setTimeout回调是宏任务的一个示例。

One of the notable differences between browsers and Node is how they prioritise micro-tasks and macro-tasks. Though the NodeJS versions ≥ v11.0.0 align with the browsers’ behaviour in terms of this aspect, NodeJS versions older than v11.0.0 have a significant difference as I’ve discussed in a previous post as well.

浏览器和Node之间的显着差异之一是它们如何区分微任务和宏任务的优先级。 尽管从这方面来看,NodeJS≥v11.0.0版本与浏览器的行为是一致的,但比我在上一篇文章中所讨论的,早于v11.0.0的NodeJS版本也具有显着差异。

It’s time to try something out! Consider the following example. In this example, we’ll be scheduling a set of promise callbacks (micro-tasks) and timer callbacks (macro-tasks) to understand how each JavaScript runtime executes it.

是时候尝试一些东西了! 考虑以下示例。 在此示例中,我们将调度一组promise回调(微任务)和计时器回调(宏任务),以了解每个JavaScript运行时如何执行它。

Microtasks vs Macrotasks
微任务与宏任务

You can also use queueMicrotask to schedule a microtask in browsers as well as Node. But for this example, I’ll be using Promise callbacks as queueMicrotask is only available from Node v11.0.0 and above.

您还可以使用queueMicrotask来安排浏览器和Node中的微任务。 但是对于此示例,我将使用Promise回调,因为queueMicrotask仅在Node v11.0.0及更高版本中可用。

If I run the above code snippet in Node v10.19.0, Node v11.0.0, Chrome 84, Firefox 78 and Safari 13.0.5, I get the following outputs from each runtime.

如果我在Node v10.19.0 ,Node v11.0.0 ,Chrome 84 ,Firefox 78和Safari 13.0.5运行上述代码片段, v10.19.0从每个运行时获取以下输出。

Image for post

As you can see, Node v11.0.0 and all the other browsers resulted in the same output, where Node v10.19.0 resulted in a slightly different output.

如您所见,Node v11.0.0和所有其他浏览器的输出相同,而Node v10.19.0输出则略有不同。

According to the HTML5 spec guideline for the event loop, the event loop should process the micro-task queue entirely, after processing one macro-task from the macro-task queue. In our example, when theset timeout3 callback is executed, it schedules a promise callback. As per the HTML5 spec, before moving to any other callback in the timer callbacks queue, the event loop has to make sure that the microtask queue is empty. Therefore it has to execute the newly added promise callback which logs inner promise3 resolved. After processing this, the micro-tasks queue becomes empty, and the event loop can move forward to process remaining set timeout1 and set timeout2 callbacks in the timer callbacks queue.

根据事件循环HTML5规范指南 ,在处理宏任务队列中的一个宏任务之后,事件循环应完全处理微任务队列。 在我们的示例中,当执行set timeout3回调时,它将安排一个promise回调。 根据HTML5规范,在移至计时器回调队列中的任何其他回调之前,事件循环必须确保微任务队列为空。 因此,它必须执行新添加的promise回调,该回调记录inner promise3 resolved 。 处理完此任务后,微任务队列将变空,并且事件循环可以前进以处理计时器回调队列中剩余的set timeout1set timeout2回调。

But in NodeJS versions before v11.0.0, the micro-task queue is drained only in between two phases of the event loop. Therefore, inner promise3 callback doesn’t get the opportunity until all the set timeout3, set timeout1 and set timeout2 callbacks are executed and the event loop attempts to move to the next phase (which is the I/O callbacks phase).

但是在v11.0.0之前的NodeJS版本中,微任务队列仅在事件循环的两个阶段之间耗尽。 因此,在执行所有set timeout3set timeout1set timeout2回调并且事件循环尝试移至下一个阶段(即I / O回调阶段)之前, inner promise3回调inner promise3机会。

If you’d like to learn more on this, I’d love it if you could check-out my previous blog post on this in-detail. 👇

如果您想了解更多有关此方面的信息,那么,如果您可以查看我以前关于此详细信息的博客文章,就可以了。 👇

嵌套计时器的行为 (The behaviour of nested Timers)

The behaviour of timers is different across NodeJS and browsers, as well as across different browser vendors/versions. Two of the most interesting facts of all are the behaviour of timers with 0 timeout, and the behaviour of nested timers.

计时器的行为在NodeJS和浏览器之间以及在不同的浏览器供应商/版本之间都不同。 所有这两个最有趣的事实是超时为0的计时器的行为以及嵌套计时器的行为。

Tip: You can create a timer with 0 timeout either by explicitly providing the timeout as 0 or by omitting the timeout parameter.

提示 :您可以通过将超时显式提供为0或省略timeout参数来创建具有0超时的计时器。

As an experiment to understand these two behaviours, let’s run the following code in Node v10.19.0, Node v11.0.0, Chrome, Firefox and Safari. This code snippet will schedule 8 nested timers with 0 expiry time and we’ll be calculating how long it took for each callback to fire since they were scheduled.

作为理解这两种行为的实验,让我们在Node v10.19.0,Node v11.0.0,Chrome,Firefox和Safari中运行以下代码。 此代码段将调度8个嵌套计时器,且其到期时间为0并且我们将计算每个回调自启动以来花费的时间。

8 nested timers!!
8个嵌套计时器!

The following table shows the result of this little experiment.

下表显示了此小实验的结果。

Image for post
How long did it take to fire each timer callback (in ms) on each JS runtime
在每个JS运行时上触发每个计时器回调(以毫秒为单位)需要多长时间

Important Note! To calculate how long it takes to fire the callbacks more or less precisely, we have used high-resolution timers using performance.now() in the browser and process.hrtime() in NodeJS. And these calculated times are rounded to two decimal values for the ease of analysis. Furthermore, these values are not 100% guaranteed to be fixed across multiple runs because the time it takes to fire the setTimeout callback will be slightly higher than the provided timeout value depending on how busy the CPU is.

重要的提示! 为了计算触发回调所需的时间,我们使用了高分辨率计时器,该计时器使用了浏览器中的performance.now()process.hrtime() 。 为了便于分析,这些计算的时间四舍五入为两个十进制值。 此外,这些值不能保证在100%多次运行中是固定的,因为触发setTimeout回调所花费的时间将略大于所提供的超时值,具体取决于CPU的繁忙程度。

A few important observations from the result of our experiment are:

从我们的实验结果中得出的一些重要发现是:

  • Even if you have set the timeout to 0, all NodeJS timers seem to have fired after at least1ms.

    即使将超时设置为0 ,所有NodeJS计时器似乎也都在至少1ms之后触发。

  • Chrome seems to cap the minimum timeout to 1ms for the first 4 nested timers. But afterwards, the cap seems to be increased to 4ms.

    Chrome似乎将前4个嵌套计时器的最小超时限制为1 1ms 。 但是之后,该上限似乎增加到了4ms

  • Unlike Chrome, Firefox doesn’t seem to cap the timeout for the first 4 timers. But similar Chrome to it caps the minimum timeout to 4ms starting from the 5th nested timer.

    与Chrome不同, Firefox似乎没有限制前4个计时器的超时时间。 但是与之类似的Chrome浏览器将从第5个嵌套计时器开始将最小超时限制为4ms

  • Safari doesn’t seem to cap the timeout for the first 5 timers. But it introduces a 4ms cap only from 6th nested timer onwards. (Unlike Chrome or Firefox)

    Safari似乎没有限制前5个计时器的超时时间。 但是它仅从第6个嵌套计时器开始才引入4ms上限。 (与Chrome或Firefox不同)

So, where does this 4ms timeout cap in the browsers come from?

那么,浏览器中的4ms超时上限是从哪里来的呢?

4ms cap for nested timers is actually described in the HTML standard. According to the standard:

HTML标准实际上描述了嵌套计时器的4ms上限。 根据标准:

“Timers can be nested; after five such nested timers, however, the interval is forced to be at least four milliseconds.”

“定时器可以嵌套; 但是,在五个这样的嵌套计时器之后,该时间间隔被强制为至少四毫秒。”

According to this rule, this cap will be applied from the 5th nested timer onwards, as we observed with Chrome and Firefox in our experiment. However, though the reason is not clear, Safari doesn’t seem to be strictly following the rule because it applies the cap from 6th nested timer instead of from 5th.

根据此规则,此上限将从第五个嵌套计时器开始应用,就像我们在实验中使用Chrome和Firefox所观察到的那样。 但是,尽管原因尚不清楚,但Safari似乎并没有严格遵守该规则,因为它从第6个嵌套计时器而不是第5个嵌套计时器开始使用上限。

Time to hack! In Firefox, this cap is configurable using dom.min_timeout_value property in about:config. By default, this is set to 4ms as per the HTML standard. Enjoying tweaking it and experimenting!

时间到了! 在Firefox中,可以使用about:config dom.min_timeout_value属性来配置此上限。 默认情况下,根据HTML标准将其设置为4ms 。 喜欢调整和尝试!

If we put the browsers aside for a moment and observe the results of Node, we can clearly see that Node doesn’t seem to care about capping the timeout based on the nesting level. Instead, both Node, as well as Chrome, share another interesting behaviour.

如果将浏览器搁置一会儿并观察Node的结果,我们可以清楚地看到Node似乎并不关心基于嵌套级别的超时限制。 相反,Node和Chrome都共享另一个有趣的行为。

Node和Chrome中所有计时器的最小超时 (Minimum timeout in all the timers in Node and Chrome)

Both NodeJS and Chrome enforces a 1ms minimum timeout to all the timers, even though they are not nested. But unlike Chrome, in NodeJS, this 1ms delay is fixed regardless of the nesting level.

即使没有嵌套,NodeJS和Chrome都对所有计时器强制执行1ms最小超时。 但是与Chrome不同,在NodeJS中,无论嵌套级别如何,此1ms延迟都是固定的。

The following is the corresponding snippet from NodeJS Timeout class where the minimum 1ms timeout is enforced to all the timers (and with a comment explaining why).

以下是NodeJS Timeout类的对应代码段,其中对所有计时器强制执行最小1ms超时(并带有解释原因的注释)。

Enforcing minimum expiry (‘after’ parameter) of 1ms in Node if the expiry is less than 1ms.
如果有效期限小于1ms,则在Node中强制执行1ms的最小有效期限(“ after”参数)。

Chrome does a similar job in its DOMTimer class. (You can also see the 4ms cap imposed when maxTimerNestingLevel is reached.)

Chrome在其DOMTimer类中做类似的工作。 (您还可以看到达到maxTimerNestingLevel时施加的4ms上限。)

As you can see, different JavaScript runtimes have their own quirky implementations in terms of nested timers and timers with 0 timeouts. It’s important to keep this in mind when developing JavaScript applications/libraries and not strictly relying on runtime-specific behaviours for better compatibility.

如您所见,就嵌套计时器和超时为0计时器而言,不同JavaScript运行时都有各自独特的实现。 在开发JavaScript应用程序/库时,请记住这一点,并且不要严格依赖于运行时特定的行为以实现更好的兼容性,这一点很重要。

process.nextTick和setImmediate (process.nextTick and setImmediate)

Another major difference between browsers and NodeJS is, process.nextTick and setImmediate.

浏览器和setImmediate之间的另一个主要区别是process.nextTicksetImmediate

process.nextTick is strictly in NodeJS only and there’s no browser counterpart for this API as of now. Although nextTick callbacks are not necessarily a part of NodeJS’s libuv event loop, nextTick callbacks are executed as a consequence of NodeJS crossing the C++/JS boundary during the event loop. So it can be considered that it’s related to the event loop in some way.

process.nextTick严格仅在NodeJS中使用,到目前为止,该API尚无浏览器。 尽管nextTick回调不一定是NodeJS libuv事件循环的一部分,但nextTick在事件循环期间越过了C ++ / JS边界 ,因此执行nextTick回调。 因此可以认为它在某种程度上与事件循环有关。

setImmediate is also a NodeJS-specific API. According to MDN and caniuse.com, setImmediate is available on IE 10, IE 11, and some earlier versions of Edge, and it’s unclear whether other browser vendors will implement this endpoint someday. However, as of this writing, it’s not a standard feature across all the browsers and shouldn’t be used in the browsers.

setImmediate还是setImmediate特定的API。 根据MDNcaniuse.com的说法setImmediate在IE 10,IE 11和Edge的某些早期版本上可用,尚不清楚其他浏览器供应商是否有一天会实现此终结点。 但是,在撰写本文时,它并不是所有浏览器的标准功能,因此不应在浏览器中使用。

If you’d like to learn more on process.nextTick and setImmediate on NodeJS, please check out my previous post on the topic. 👇

如果您想了解有关setImmediate process.nextTicksetImmediate更多信息,请查看我以前关于该主题的文章。 👇

I hope this article was helpful, but I could only cover just the tip of the iceberg. Feel free explore more, and let me know your findings as a comment. Any feedback is always welcome! Cheers!

我希望这篇文章对您有所帮助,但我只能讲冰山一角。 随意探索更多,并通过评论让我知道您的发现。 随时欢迎任何反馈! 干杯!

翻译自: https://blog.insiderattack.net/javascript-event-loop-vs-node-js-event-loop-aea2b1b85f5c

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值