如何利用JavaScript和Node.js中的事件循环? [英] How to leverage event loop in JavaScript and Node.js?

查看:126
本文介绍了如何利用JavaScript和Node.js中的事件循环?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

事件循环的概念是一般还是特定于语言? 我正在寻找具体例子,以清楚理解以下内容:



1。如何工作?


  1. 如何/何时可以利用 JavaScript 中的事件循环?

  2. 关于事件循环的 ECMAScript-6/2015 中引入的任何更改?

更新:
这里没有答案,但我正在寻找一个容易理解的定义与示例。



附录:



请考虑以下代码:

  var list = readHugeList(); 

var nextListItem = function(){
var item = list.pop();

if(item){
//处理列表项...
setTimeout(nextListItem,0);
}
};假设setTimeout()这里使用事件循环来防止stackoverflow,是否正确?我读了这个代码的解释:


堆栈溢出被消除,因为事件循环处理递归而不是通话堆栈。




更新2



对于上面代码中的setTimeout()如下:


堆栈溢出被消除,因为事件循环处理递归,而不是调用堆栈。当 nextListItem 运行时,如果item不为空,则将超时函数(nextListItem)推送到事件队列,并退出该函数,从而使调用堆栈清除。当事件队列运行其超时事件时,将处理下一个项目,并将定时器设置为再次调用nextListItem。因此,无需直接递归调用,该方法从头到尾处理,因此调用堆栈保持清除,无论迭代次数如何。


解释中的矛盾,上述和这里的答案正在使它变得更加难以理解。

解决方案


事件循环的概念是一般的还是针对语言的?


其最高水平,一般。但是,不同的环境将不同的细节实现细节,真正需要环境特定的信息来了解发生了什么。


它是如何工作的? / p>

所有的血液细节都在规范中,主要是在作业和作业排队



有几个关键方面:


  1. >作业(JavaScript规范的术语)或任务(如HTML规范所称),等待JavaScript引擎运行。当发动机完成作业时,它会在队列中的下一个作业/任务(如果有的话)上起作用。


  2. 一旦作业开始,它一直运行直到完成;没有其他工作可以中断它。这被称为运行完成,对JavaScript的工作原理非常重要。


  3. 处理作业队列


请考虑以下代码:

 的console.log( 1); 
setTimeout(function(){
console.log(two);
},1000);

如果您运行该代码(通过NodeJS或浏览器),则会发生什么(省略一些不相关的详细信息):


  1. 开始时,队列为空,JavaScript引擎空闲

  2. 环境(NodeJS,浏览器)排队作业运行脚本

  3. JavaScript引擎拾取作业并运行脚本:


    • 它输出一个

    • 它为我们给 setTimeout
    • 的匿名函数设置一个计时器
    • 作业结束


  4. 在某些时候,环境中的定时器机制决定是时候调用回调,所以它排队一个工作来调用它

  5. JavaScript引擎从队列中获取该作业并运行函数


    • 它输出两个

    • 作业结束


现在考虑这个代码:

  console.log(Date.now ),一); 
setTimeout(function first(){
console.log(Date.now(),two);
},500);
setTimeout(function second(){
var end = Date.now()+ 1000;
while(end> Date.now()){
// Busy-等待(通常这是一个坏事,我在这里使用
//来模拟需要大量时间的实际工作
}
},100);

正如你所看到的,它调度一个定时回调500ms,然后另一个在100ms,但在100ms的回调代码将需要至少1000ms才能运行,会发生什么?


  1. 环境排队作业运行脚本

  2. JS引擎选择这个工作


    • 输出时间值和一个,说 1470727293584一个

    • 将来为500ms的第一个设置定时回调

    • 将功能 second 定时回调

    • 作业结束


  3. 大约100ms后,环境将作业排队运行第二个

  4. JavaScript引擎拾取作业并运行函数


    • 开始做工作(好的,真的只是忙着等待)


  5. 大约400ms后时间设置定时器),环境队列作业调用首先;由于JavaScript引擎正忙于上一个作业,所以该作业位于队列中。

  6. 同时,JavaScript引擎仍然在调用第一个


    • 最终,#3的JavaScript引擎完成的工作已完成

    • 作业完成


  7. JavaScript引擎从队列中选择下一个作业,并将调用运行到 second


    • 输出时间值和两,如 1470727294687两个

    • 作业结束


请注意环境在JavaScript忙时执行某些操作;它排队了要完成的工作。



在JavaScript引擎正忙的时候,环境可以做的事情非常重要。 (值得注意的是,当排队作业时,某些环境可能不一定会在队列的之间添加任务;在某些浏览器中,例如队列实际上是一个具有稍微不同优先级的队列...)


如何/何时应该/我可以利用它?


它不是很有用,因为知道它在那里。同样重要的是要注意,虽然JavaScript 具有运行到完成的语义,但运行环境的环境可能会同时执行其他操作,即使JavaScript代码正在运行。


在ECMAScript-6/2015中有关事件循环引入的任何更改?


不是真的,虽然它在规范中的定义比以前更完整。可能最接近更改的内容与承诺有关:您计划的回调与然后 catch 将始终排队等待作业,它将永远不会同步运行。这是JavaScript规范首次定义了异步发生的事情(ajax和timers不属于JavaScript规范)。






以下,您已询问:


请考虑以下代码:

  var list = readHugeList(); 
var nextListItem = function(){
var item = list.pop();
if(item){
//处理列表项...
setTimeout(nextListItem,0);
}
};

假设setTimeout()这里使用事件循环来防止堆栈溢出是正确的吗?


(我假设在此之后立即有一个 nextListItem(); 。)



不,但它做的其他重要事情。非 setTimeout 版本将如下所示: 1

  var list = readHugeList(); 
while(list.length){
var item = list.pop();
//处理列表项...
}

这是一个简单的循环,没有堆栈溢出的潜力。



它正在做的是与事件循环协同工作,避免了一个真正长期运行的工作,将绑定JavaScript引擎,阻止它处理任何其他作业(如I / O完成,点击或其他事件)。因此,它通过一次处理一个项目将工作打破小作业,有助于确保整个作业队列不断得到处理。这意味着处理列表需要更长的时间,但是在执行此操作时不会阻止其他操作。






< sup> 1 确实,代码的非 setTimeout 版本可能如下所示:

  var list = readHugeList(); 
var nextListItem = function(){
var item = list.pop();
if(item){
//处理列表项...
// then recurse
nextListItem();
}
};
nextListItem();

...在这种情况下,有堆栈溢出的潜力,但这将是一个非常奇怪写这个代码的方法。


Is the concept of event loop general or specific to languages? I am looking for detailed explanations with examples to clearly understand the following:

1. how it works?

  1. How/when should/can I leverage event loop in JavaScript?
  2. Any changes introduced in ECMAScript-6/2015 regarding event loop?

Update: There's no dearth of answers to this but I'm looking for an easy to understand definition with examples.

Addendum:

Consider the following code:

var list = readHugeList();

var nextListItem = function() {
    var item = list.pop();

    if (item) {
        // process the list item...
        setTimeout( nextListItem, 0);
    }
};  

Is it correct to assume setTimeout() here uses the event loop to prevent stackoverflow? I read an explanation for this code that:

The stack overflow is eliminated because the event loop handles the recursion, not the call stack.

Update 2

The full explanation given for the setTimeout() in the code above is as follows:

The stack overflow is eliminated because the event loop handles the recursion, not the call stack. When nextListItem runs, if item is not null, the timeout function (nextListItem) is pushed to the event queue and the function exits, thereby leaving the call stack clear. When the event queue runs its timed-out event, the next item is processed and a timer is set to again invoke nextListItem. Accordingly, the method is processed from start to finish without a direct recursive call, so the call stack remains clear, regardless of the number of iterations.

The contradiction in explanations, aforementioned and in the answers here, is making it all the more difficult to understand.

解决方案

Is the concept of event loop general or specific to languages?

At its highest level, general. But different environments will implement the details so differently that really you need environment-specific information to know what's going on.

how it works?

All the gory details are in the specification, chiefly in the section called Jobs and Job Queues.

There are a couple of keys aspects:

  1. There is a queue of jobs (the JavaScript spec's term) or "tasks" (as the HTML spec calls them) waiting to be run by the JavaScript engine. When the engine finishes a job, it's put to work on the next job/task in the queue, if any.

  2. Once a job is started, it keeps running until it's done; no other jobs can interrupt it. This is called run-to-completion and is very important to the definition of how JavaScript works.

  3. The job queue is processed in order.

So consider this code:

console.log("one");
setTimeout(function() {
    console.log("two");
}, 1000);

If you run that code (via NodeJS or a browser), here's what happens (omitting some irrelevant details):

  1. At the beginning, the queue is empty and the JavaScript engine is idle
  2. The environment (NodeJS, the browser) queues a job to run the script
  3. The JavaScript engine picks up the job and runs the script:
    • It outputs "one"
    • It sets a timer for the anonymous function we gave to setTimeout
    • The job ends
  4. At some point, the timer mechanism in the environment determines that it's time to call the callback, so it queues a job to call it
  5. The JavaScript engine picks up that job from the queue and runs the function
    • It outputs "two"
    • The job ends

Now consider this code:

console.log(Date.now(), "one");
setTimeout(function first() {
    console.log(Date.now(), "two");
}, 500);
setTimeout(function second() {
    var end = Date.now() + 1000;
    while (end > Date.now()) {
        // Busy-wait (normally this is a Bad Thing™, I'm using it here
        // to simulate actual work that takes significant time
    }
}, 100);

As you can see, it schedules a timed callback at 500ms and then another at 100ms. But the code in the callback at 100ms will take at least 1000ms to run. What happens?

  1. The environment queues a job to run the script
  2. The JS engine picks that job up
    • Outputs the time value and "one", say 1470727293584 one
    • Sets a timed callback to the function first for 500ms in the future
    • Sets a timed callback to the function second for 100ms in the future
    • The job ends
  3. About 100ms later, the environment queues a job to run second
  4. The JavaScript engine picks up the job and runs the function
    • It starts doing work (okay, so really it's just busy waiting)
  5. About 400ms later (500ms from the time the timer was set), the environment queues a job to call first; since the JavaScript engine is busy with the previous job, the job sits in the queue
  6. Meanwhile, the JavaScript engine is still working on the job calling first:
    • Eventually the work being done by the JavaScript engine from #3 is done
    • The job finishes
  7. The JavaScript engine picks up the next job from the queue and runs the call to second
    • It outputs the time value and "two", say 1470727294687 two
    • The job ends

Note that the environment did something while JavaScript was busy; it queued a job to be done.

The fact that the environment can do things while the JavaScript engine is busy is very important.

(It's worth noting that when queuing jobs, some environments may not necessarily add the job at the end of the queue; in some browsers, for instance, "the queue" is actually more than one queue with slightly different priorities...)

how/when should/can I leverage it?

It's not so much leveraging it as knowing it's there. It's also important to note that while JavaScript has run-to-completion semantics, the environment in which it's running may be doing other things at the same time, even while the JavaScript code is running.

any changes introduced in ECMAScript-6/2015 regarding event loop?

Not really, although it's defined much more completely in the spec than previously. Probably the closest thing to a change is in relation to promises: The callback you schedule with then or catch will always be queued as a job, it will never be run synchronously. That's the first time the JavaScript specification has defined something that happens asynchronously (ajax and timers are not part of the JavaScript spec).


Below, you've asked:

Consider this code:

var list = readHugeList();
var nextListItem = function() {
    var item = list.pop();
    if (item) {
        // process the list item...
        setTimeout(nextListItem, 0);
    }
};

Is it correct to assume setTimeout() here uses the event loop to prevent stackoverflow?

(I assume there's a nextListItem(); call immediately after that.)

No, but it's doing something else important. The non-setTimeout version of that would look something like this:1

var list = readHugeList();
while (list.length) {
    var item = list.pop();
    // process the list item...
}

That's a simple loop, there's no potential for stack overflow.

What it's doing is working cooperatively with the event loop, avoiding having a really long-running job that would tie up the JavaScript engine, preventing it processing any other jobs (such as I/O completions, clicks, or other events). So it's breaking the work up into small jobs by processing the items one at a time, helping ensure that the overall job queue keeps getting processed. That means it takes a lot longer to process the list, but doesn't block other things while doing so.


1 It's true that the non-setTimeout version of that code could look like this:

var list = readHugeList();
var nextListItem = function() {
    var item = list.pop();
    if (item) {
        // process the list item...
        // then recurse
        nextListItem();
    }
};
nextListItem();

...and in that case, there's potential for stack overflow, but it would be a very odd way to write that code.

这篇关于如何利用JavaScript和Node.js中的事件循环?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆