当 Promise 的解析取决于 setTimeout 时,JS 事件循环的行为如何?


console.log('1')
setTimeout(() => {
console.log('2')
}, 0)
function three() {
return new Promise(resolve => {
setTimeout(() => {
return new Promise(resolve => resolve('3'))
},0)
})  
}
three().then(result => console.log(result))
console.log('4')

此代码段输出1 4 2

这是基于我对javascript的事件循环和并发模型的理解所期望的行为。但这给我留下了一些挥之不去的问题。

在回答这些问题之前,我将首先分析一下我对这个代码片段的理解。

为什么代码输出1

无需解释

为什么代码输出4

输出2的回调在0ms后加载到事件队列(也称为宏任务队列)中,但直到主调用堆栈清空后才执行。

即使three是一个立即被解析的promise,它的代码也会被加载到作业队列(也称为微任务队列)中,直到主调用堆栈被清空(无论事件队列的内容如何)才会执行

为什么代码输出2

console.log(4)之后,主调用堆栈为空,javascript查找要加载到主堆栈上的下一个回调。可以很安全地假设,在这一点上,一些"工作线程"已经将输出2的回调函数放入宏任务队列中。这将加载到堆栈中,并输出2。

为什么代码不输出3

函数three返回的promise在主线程中是then-ed。通过then传递的回调函数被加载到微任务队列中,并在宏任务队列中的下一个任务之前执行。因此,虽然您可能认为它会在记录2的回调之前运行,但实际上理论上它根本不可能运行。这是因为Promise只能通过其setTimeout的回调函数来解析,而回调函数(因为setTimeout)只有在主执行线程(等待Promise解析的同一线程)为空时才会运行。

为什么这会困扰我

我试图建立一个完整的javascript处理并发的理论心理模型。该模型中缺少的部分之一是网络请求、承诺和事件循环之间的关系。以上面的代码片段为例,假设我将three的setTimeout替换为某种网络请求(异步web开发中非常常见的事情)。假设网络请求的行为与setTimeout类似,即当"工作线程"完成时,回调被推送到宏任务队列,我很难理解回调是如何执行的。但这确实是一直在发生的事情。

有人能帮我理解吗?在我目前对js并发性的理解中,是否有遗漏?我做了一个错误的假设吗?这些真的有意义吗?lol

为什么代码不输出3

在此代码中:

function three() {
return new Promise(resolve => {
setTimeout(() => {
return new Promise(resolve => resolve('3'))
},0)
})  
}
three().then(result => console.log(result))

您永远不会解决three()创建的第一个Promise。因为这是从three()返回的,所以three().then(...)中的.then()处理程序永远不会被调用。

您确实解析了在计时器内创建的promise,但您只将该promise返回给计时器回调,该回调不起任何作用。

如果您将代码更改为:

function three() {
return new Promise(resolve => {
setTimeout(() => {
resolve('3');
},0)
})  
}
three().then(result => console.log(result))

然后,您将看到3得到输出。

因此,这与事件循环或其工作方式无关。这与不解析three()返回的promise有关,因此该promise上的.then()处理程序永远不会被调用。


我正在尝试构建一个关于javascript如何处理并发的完整理论心理模型。该模型中缺少的部分之一是网络请求、承诺和事件循环之间的关系。以上面的代码片段为例,假设我将three的setTimeout替换为某种网络请求(异步web开发中非常常见的事情)。假设网络请求的行为与setTimeout类似,即当"工作线程"完成时,回调被推送到宏任务队列,我很难理解回调是如何执行的。但这确实是一直在发生的事情。

网络请求、承诺和计时器都经过事件循环。关于如何同时对队列中的多个事件进行优先级排序,存在非常复杂的规则。CCD_ 15处理程序通常首先进行优先级排序。

把Javascript解释器想象成这个简单的序列。

Get event from event queue
If nothing in the event queue, sleep until something is in the event queue
Run callback function associated with the event you pull from the event queue
Run that callback function until it returns
Note, it may not be completely done with its work because it may
have started other asynchronous operations and set up its own
callbacks or promises for those.  But, it has returned from the
original callback that started it
When that callback returns, go back to the first step above and get the next event

请记住,node.js中的网络请求、承诺、定时器以及所有异步操作都是以这种方式通过事件队列的。

为什么您认为网络请求会表现为setTimeout?这是一个承诺,resolved()将进入微任务

最新更新