为啥这个异步函数在它之前定义的等效 Promise.then 链之前执行?

Posted

技术标签:

【中文标题】为啥这个异步函数在它之前定义的等效 Promise.then 链之前执行?【英文标题】:Why does this async function execute before the equivalent Promise.then chain defined before it?为什么这个异步函数在它之前定义的等效 Promise.then 链之前执行? 【发布时间】:2020-10-05 21:16:25 【问题描述】:

我有以下代码:

var incr = num => new Promise(resolve => 
  resolve(num + 1);
);

var x = incr(3)
  .then(resp => incr(resp))
  .then(resp => console.log(resp));

async function incrTwice(num) 
  const first = await incr(num);
  const twice = await incr(first);
  console.log(twice);


incrTwice(6);

我认为(也许是错误地)展示了实现相同功能的两种等效方法:第一种是链接 promise,第二种是使用 async/await 的语法糖。

我希望承诺链解决方案首先到 console.log,然后是异步函数,但是异步函数 console.log 首先是承诺链解决方案。

我的逻辑如下:

    xs 在处理声明时,初始解析将首先出现在微任务队列中 xincrTwice 声明之间的堆栈为空,这将导致微任务队列被刷新(导致承诺链完成) x 先打印 incrTwice 已定义 incrTwiceawaits 的微任务队列上执行排队,最终打印到控制台 incrTwice 打印第二次

显然我在某个地方有误会,有人能指出我错在哪里吗?

【问题讨论】:

【参考方案1】:

首先,让我指出,你永远不应该争论独立承诺链的执行顺序。有两个异步调用,它们不相互依赖,而是同时运行,所以它们应该总是按任意顺序完成。

仅使用立即解决承诺的玩具示例使此顺序取决于微任务队列语义而不是实际的异步任务,这使其成为纯粹的学术练习(规范中的 result is subject to changes)。

不管怎样,让我们​​澄清你的误解:

xincrTwice 的声明之间堆栈为空,这将导致微任务队列被刷新

不,堆栈只有在 所有 用户代码运行完成后才会变为空。堆栈上仍有<script> 元素的全局执行上下文。在所有同步代码(incr = …x = incr(3).…incrTwice(6))完成之前,不会执行任何微任务。

我相信 [代码] 显示了实现相同功能的两种等效方法:第一种是链接 promise,第二种是使用 async/await 的语法糖。

不完全是。当取消嵌套从第一个 .then(…) 处理程序返回的 incr(resp) 承诺时,.then() 链接有一个额外的解析步骤。要使其行为与incrTwice 中的awaited 承诺完全相同,您需要编写

incr(3).then(resp =>
  incr(resp).then(resp =>
    console.log(resp)
  )
);

如果这样做,您实际上会按照启动两个承诺链的顺序获得 console 日志,因为在执行 console.log() 之前,它们将执行相同数量的微任务。

更多详情请见What is the order of execution in javascript promises、Resolve order of Promises within Promises、What happen when we return a value and when we return a Promise.resolve from a then() chain, in the microtask queue?、What is the difference between returned Promise?、ES6 promise execution order for returned values

【讨论】:

【参考方案2】:

你的想法是可以理解和合乎逻辑的。观察到的行为的原因与 Promise API 中内置的保证之一有关,即 promise 在执行中始终是异步的,即使它们执行同步操作(如解决 promise立即地)。从技术上讲,这意味着在 当前运行 完成之前永远不会调用 Promise 的回调。

作为 MDN puts it:

在 JavaScript 事件循环的 completion of the current run 之前永远不会调用回调。

所以:

Promise.resolve(10).then(value => console.log(value));
console.log(20); //20, 10 - NOT 10, 20

我在我的 Promise 指南中介绍了这一点,可以是 found here。

【讨论】:

是的,但在我的示例中,两个“版本”都是承诺。我知道在同一脚本中的任何同步代码之后,一个承诺将解决,因为承诺将一个微任务排队,该微任务在堆栈清空之前不会被刷新。我感兴趣的是两个promise之间的微任务队列交错,而不是promise和一段同步代码之间的交错。

以上是关于为啥这个异步函数在它之前定义的等效 Promise.then 链之前执行?的主要内容,如果未能解决你的问题,请参考以下文章

jQuery UI 自动完成:你如何启动一个异步进程并在它完成之前退出它

当异步函数不应该返回 Promise 时,为啥我需要等待它?

为啥我的异步函数返回 Promise <pending> 而不是一个值?

在返回函数的变量之前,如何等待 promise 完成?

# Promise的简单理解和基本使用

# Promise的简单理解和基本使用