理解事件循环

30 浏览
0 Comments

理解事件循环

我正在考虑这个问题,以下是我得出的结论:\n让我们看下面的代码:\n

console.clear();
console.log("a");
setTimeout(function(){console.log("b");},1000);
console.log("c");
setTimeout(function(){console.log("d");},0);

\n一个请求进来后,JS引擎逐步执行上述代码。前两个调用是同步调用。但是当遇到setTimeout方法时,它变成了异步执行。但是JS立即从中返回并继续执行,这被称为非阻塞异步。然后继续处理其他操作。\n此执行的结果如下:\n

\na c d b\n

\n所以基本上第二个setTimeout先完成,它的回调函数比第一个先执行,这是有道理的。\n我们在讨论单线程应用程序。JS引擎继续执行,除非它完成了第一个请求,否则它不会去执行第二个请求。但好的是,它不会等待setTimeout等阻塞操作解决,因此速度会更快,因为它接受新的传入请求。\n但我的问题围绕以下几点展开:\n#1: 如果我们在讨论单线程应用程序,那么在JS引擎接受更多请求并执行它们的同时,是什么机制处理setTimeouts?单线程如何继续处理其他请求?在其他请求不断进来并被执行时,setTimeout是如何工作的。\n#2: 如果这些setTimeout函数在更多请求进来并被执行的同时在后台执行,是什么执行了后台的异步操作?我们所说的事件循环是什么东西?\n#3: 但是是否应该将整个方法放在事件循环中,以便整个过程得到执行并调用回调方法?当谈论回调函数时,这是我的理解:\n

function downloadFile(filePath, callback)
{
   blah.downloadFile(filePath);
   callback();
}

\n但在这种情况下,JS引擎如何知道它是否是异步函数,以便将回调放入事件循环中?也许类似C#中的async关键字或某种指示JS引擎将采用的方法的属性,表明它是一个异步方法,应该相应地处理。\n#4: 但是一篇文章与我对事情如何工作的猜测相反:\n

\n事件循环是一个回调函数队列。当异步函数执行时,回调函数被推入队列中。JavaScript引擎在异步函数执行后的代码之后开始处理事件循环。\n

\n#5: 这里有一张图片可能有所帮助,但图片中的第一个解释与第4个问题中提到的内容完全相同:\n\"enter\n所以我的问题是关于上述问题的一些澄清。

0
0 Comments

事件循环(Event Loop)的主要任务是监视调用栈(Call Stack)、回调队列(Callback Queue)和微任务队列(Micro task queue)。如果调用栈为空,事件循环将从微任务队列中取出第一个事件,然后从回调队列中取出第一个事件,并将其推入调用栈中,从而运行它。这样的迭代被称为事件循环中的一个“tick”。

大多数开发人员知道,JavaScript是单线程的,这意味着JavaScript中的两个语句不能并行执行,这是正确的。执行是逐行进行的,这意味着每个JavaScript语句都是同步和阻塞的。但是,如果你使用setTimeout()函数,一个由浏览器提供的Web API,你可以以异步方式运行代码,该函数确保你的代码在指定的时间(以毫秒为单位)之后执行。

例如:

console.log("Start");
setTimeout(function cbT(){
console.log("Set time out");
},5000);
fetch("http://developerstips.com/").then(function cbF(){
console.log("Call back from developerstips");
});
// 数百万行代码
// 例如需要10000毫秒才能执行完
console.log("End");

setTimeout接受一个回调函数作为第一个参数,以毫秒为单位的时间作为第二个参数。

在浏览器控制台执行以上语句后,将会打印出:

Start
End
Call back from developerstips
Set time out

注意:你的异步代码将在所有同步代码执行完毕后执行。

理解代码逐行执行的过程:

JS引擎执行第一行,并在控制台打印“Start”。

在第二行,它看到了名为cbT的setTimeout函数,并将cbT函数推入回调队列。

然后,指针直接跳到第7行,它会看到promise,并将cbF函数推入微任务队列。

然后它执行数百万行的代码,在最后打印“End”。

在主线程执行结束后,事件循环首先检查微任务队列,然后再检查回调队列。在我们的例子中,它从微任务队列中取出cbF函数,并将其推入调用栈,然后从回调队列中取出cbT函数,并将其推入调用栈。

事件循环最初不会从微任务队列中获取任务。事实上,它会从任务队列中获取第一个任务,如果任务队列为空,则会检查微任务队列。无论哪种方式,最旧的任务将首先执行。例如,如果你有一个setTimeout为0和一个Promise...那么Promise的回调函数将首先执行,因为setTimeout为0意味着最少需要4毫秒才能入队,只有在时间过去后才将其入队。这是模型:html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model

微任务队列始终优先级最高,请参考developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide的相关说明。

你是否尝试阅读事件循环模型?

你是否尝试将setTimeout延迟设置为0毫秒(对于cbT回调函数)?这是否证明了微任务队列比回调队列具有更高的优先级?当你使用较长的超时时,我认为只是setTimeout API在这个大的超时之后将回调函数放入回调队列,而事件循环已经遍历了微任务队列。有道理吗?也许证据在这里(消除了fetch实现、缓存等的具体细节):developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/…

关于可用队列,addEventListener()是如何工作的?比如,如果你为“click”事件添加一个事件监听器回调函数,当发生点击时或者模拟分发一个点击事件时,它会在什么时候被调用?

如果你为一个事件添加了addEventListener(),那么这个事件可以被其他事件直接或间接地调用。

文章来源:

https://stackoverflow.com/questions/46114752

0
0 Comments

理解事件循环的出现原因和解决方法

在编写JavaScript代码时,我们需要理解事件循环的工作原理,以便能够正确地处理异步操作和事件。事件循环的出现原因和解决方法。

首先,我们需要明白宿主进程不是单线程的,除了背景工作线程之外。背景工作线程会使情况变得复杂。因此,所有的JavaScript代码都在同一个线程上运行,不可能同时执行两个不同的部分(所以不会出现并发管理的噩梦)。

正在执行的JavaScript代码是宿主进程从事件循环中选择的最后一段代码。

在代码中,我们可以做两件事情:运行同步指令和安排在某些事件发生时执行的函数。

下面是对示例代码的心理表示(注意:这只是我对浏览器实现细节的理解,不一定准确!):

console.clear();                                   //同步执行
console.log("a");                                  //同步执行
setTimeout(                //安排在1秒后执行inAWhile函数
    function inAWhile(){
        console.log("b");
    },1000);    
console.log("c");                                  //同步执行
setTimeout(
    function justNow(){          //立即执行justNow函数
        console.log("d");
},0);       

当代码运行时,宿主进程的另一个线程会跟踪所有正在发生的系统事件(UI上的点击、文件读取、网络数据包接收等)。

当代码执行完毕时,它将从事件循环中移除,并且宿主进程会返回检查是否有更多的代码需要运行。事件循环中包含了另外两个事件处理器:一个立即执行(justNow函数)和另一个在一秒后执行(inAWhile函数)。

宿主进程现在会尝试匹配所有已发生的事件,看是否有为它们注册的处理器。

如果找到了justNow正在等待的事件,它将开始执行其代码。当justNow函数退出时,它再次检查事件循环,寻找事件处理器。假设已经过去了1秒,它将执行inAWhile函数,以此类推......

setTimeout是在主线程中实现的,因此在示例代码中没有任何需要单独线程的内容。实际上,在浏览器中,只有选项卡是在多个线程中实现的。在单个选项卡中,所有的进程,包括多个并行网络连接、等待鼠标点击、setTimeout、动画等,都在同一个线程中执行。

0
0 Comments

理解事件循环的出现原因以及解决方法

在单线程应用程序中,JS引擎接受更多的请求并执行它们时,谁来处理setTimeout呢?单线程是否会继续处理其他的请求呢?那么在其他请求不断到来并被执行的同时,谁会继续处理setTimeout呢?

事实上,在node进程中只有一个线程会执行程序的JavaScript代码。然而,在node内部,实际上有几个线程来处理事件循环机制的操作,其中包括一个IO线程池和一些其他线程。关键在于,这些线程的数量与处理的并发连接数不一致,不像线程-连接并发模型一样。

关于“执行setTimeout”,当你调用setTimeout时,node实际上只是更新一个数据结构,该结构包含将在将来某个时间执行的函数。它基本上有一堆需要做的任务队列,每当事件循环的一个“tick”到来时,它会选择一个任务,将其从队列中删除并执行。

需要理解的关键一点是,大部分重活node依赖于操作系统完成。所以,传入的网络请求实际上是由操作系统本身跟踪的,当node准备好处理一个请求时,它只需使用系统调用向操作系统请求一个带有准备好的数据的网络请求。所以,node的大部分IO“工作”要么是“嘿,操作系统,是否有准备好的网络连接需要读取的数据?”或者“嘿,操作系统,我的哪个未完成的文件系统调用有准备好的数据?”根据其内部算法和事件循环引擎设计,node将选择一个要执行的JavaScript“tick”,运行它,然后再次重复这个过程。这就是所谓的事件循环。Node基本上始终在确定“下一个要执行的JavaScript代码是什么?”然后执行它。这还涉及操作系统已经完成的IO,以及通过调用setTimeout或process.nextTick在JavaScript中排队等待的任务。

这是正确的,但有些误导。关键是一般的模式是:

//假设这段代码在tick 1中运行
fs.readFile("/home/barney/colors.txt", function (error, data) {
  //这个回调函数中的代码绝对不会在tick 1中执行
  //它将在某个tick(>= 2)中执行
});
//这段代码也绝对会在tick 1中执行
//然而,通常这里没有太多其他要做的事情,
//所以在排队一些异步IO之后,这个tick
//将没有任何有用的工作要做,所以它将会结束,
//因为在可以执行任何有用的工作之前,IO的结果是必要的

所以,是的,你完全可以通过在同一个tick中同步计算斐波那契数列来阻塞事件循环,这将完全冻结你的程序。这是一种合作式的并发。JavaScript的每个tick都必须在合理的时间内让出事件循环,否则整个架构将失败。

通常情况下,它将被放到队列的末尾,但是process.nextTick、setTimeout和setImmediate的语义略有不同,尽管你不应该真的关心这些。我在setTimeout and friends这篇博文中详细讨论了这个问题。

有一个固定的一组node核心函数是异步的,因为它们会进行系统调用,node知道哪些是异步的,因为它们必须调用操作系统或C++。基本上,所有的网络和文件系统IO以及子进程交互都是异步的,JavaScript让node运行异步代码的唯一方式是调用node核心库提供的异步函数。即使你使用的是一个定义了自己API的npm包,为了让事件循环继续执行,最终这个npm包的代码将调用一个node核心的异步函数,这时node知道这个tick已经完成,可以开始事件循环算法了。

没有JavaScript会在后台执行。你程序中的所有JavaScript代码都是前台运行的,一个接一个地执行。在后台发生的是操作系统处理IO,而node等待IO准备就绪,并管理其等待执行的JavaScript队列。

JS引擎如何知道一个函数是否是异步函数,以便将其放入事件循环中?

在node核心中有一组固定的函数是异步的,因为它们会进行系统调用,node知道哪些函数是异步的,因为它们必须调用操作系统或C++。基本上,所有的网络和文件系统IO以及子进程交互都是异步的,JavaScript让node运行异步代码的唯一方式是调用node核心库提供的异步函数。即使你使用的是一个定义了自己API的npm包,为了让事件循环继续执行,最终这个npm包的代码将调用一个node核心的异步函数,这时node知道这个tick已经完成,可以开始事件循环算法了。

事件循环是一个回调函数的队列。当一个异步函数执行时,回调函数会被推入队列中。JavaScript引擎在异步函数之后的代码执行之前不会开始处理事件循环。

是的,这是正确的,但是这样说有些误导。关键是正常的模式是:

//假设这段代码在tick 1中运行
fs.readFile("/home/barney/colors.txt", function (error, data) {
  //这个回调函数中的代码绝对不会在tick 1中执行
  //它将在某个tick(>= 2)中执行
});
//这段代码也绝对会在tick 1中执行
//然而,通常这里没有太多其他要做的事情,
//所以在排队一些异步IO之后,这个tick
//将没有任何有用的工作要做,所以它将会结束,
//因为在可以执行任何有用的工作之前,IO的结果是必要的

所以是的,你完全可以通过在同一个tick中同步计算斐波那契数列来阻塞事件循环,这将完全冻结你的程序。这是一种合作式的并发。每个JavaScript的tick必须在合理的时间内让出事件循环,否则整个架构将失败。

"tick"在技术层面上是什么意思?

在技术层面上,我认为"tick"的意思是事件循环运行JavaScript代码的单个实例。我猜其他人可能认为它是事件循环代码的单个迭代(这可能包括没有操作的情况,例如当没有准备好运行的JS时)。

0