【问题标题】:How does the JS event loop behave when a Promise's resolution depends on setTimeout?当 Promise 的解析依赖于 setTimeout 时,JS 事件循环如何表现?
【发布时间】:2019-10-30 06:17:49
【问题描述】:
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')

这段代码sn-p输出1 4 2

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

在回答这些问题之前,我先分解一下我对这段代码 sn-p 的理解。

为什么代码输出 1

不需要解释

为什么代码输出 4

输出 2 的回调在 0 毫秒后被加载到事件队列(又名宏任务队列)中,但直到主调用堆栈被清空后才会执行。

即使three 是一个立即解决的承诺,它的代码也会被加载到作业队列(又名微任务队列)中,并且在主调用堆栈被清空之前不会被执行(不管事件的内容如何)队列)

为什么代码输出 2

console.log(4) 之后,主调用堆栈为空,javascript 寻找下一个回调以加载到主堆栈上。可以肯定的是,此时某个“工作线程”已经将输出 2 的回调函数放入宏任务队列中。这被加载到堆栈上并输出 2。

为什么代码不输出 3

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

为什么这让我烦恼

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

谁能帮我理解?我目前对 js 并发性的理解有什么遗漏吗?我做了不正确的假设吗?这真的有任何意义吗?哈哈

【问题讨论】:

  • 您正在覆盖您的 resolve 回调,该回调永远不会被调用,因此您的承诺始终处于未决状态。
  • return new Promise(resolve => resolve('3')) 更改为return new Promise(resolve2 => resolve('3'))。但是,在 settimeout 的回调中没有任何承诺。

标签: javascript node.js multithreading promise


【解决方案1】:

为什么代码不输出 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() 处理程序。

您确实解决了在计时器内创建的承诺,但您只是将该承诺返回给什么都不做的计时器回调。

如果您将代码更改为:

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

然后,您会看到3get 输出。

所以,这与事件循环或它的工作方式没有任何关系。它与不解决 three() 返回的承诺有关,因此该承诺上的 .then() 处理程序永远不会被调用。


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

网络请求、promise 和计时器都经过事件循环。关于队列中的多个事件如何相对于彼此进行优先级排序,有非常复杂的规则。 .then() 处理程序通常优先。

把 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 中的网络请求、承诺、计时器和字面上的所有异步操作都以这种方式通过事件队列。

【讨论】:

    【解决方案2】:

    为什么你认为网络请求会表现为 setTimeout? 这是一个 Promise,resolved() 将用于微任务

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-11-06
      • 1970-01-01
      • 1970-01-01
      • 2012-03-15
      • 2016-09-21
      相关资源
      最近更新 更多