【问题标题】:Confusion with how JS engine runs promises?对 JS 引擎如何运行 Promise 感到困惑?
【发布时间】:2020-02-12 08:10:25
【问题描述】:

我是 JS 新手,正在学习 promise。所以,假设我们有这样的代码:

new Promise(function(resolve, reject) {

  setTimeout(() => resolve(1), 1000); // (*)

}).then(function(result) { // (**)

  alert(result); // 1
  return result * 2;

})

如您在上面的代码中看到的,当调用 promise 时,setTimeout 通过回调队列运行。问题是当 setTimeOut 被发送到浏览器时,JS 引擎是否会省略 .then() 并继续运行其余代码,直到 promise 解决?其次,async/await 示例:

async function showAvatar() {

  // read our JSON
  let response = await fetch('/article/promise-chaining/user.json');
  let user = await response.json();

  // read github user
  let githubResponse = await fetch(`https://api.github.com/users/${user.name}`);
  let githubUser = await githubResponse.json();

  // show the avatar
  let img = document.createElement('img');
  img.src = githubUser.avatar_url;
  img.className = "promise-avatar-example";
  document.body.append(img);

  // wait 3 seconds
  await new Promise((resolve, reject) => setTimeout(resolve, 3000));

  img.remove();

  return githubUser;
}

showAvatar();

调用showAvatar函数时,JS引擎会遇到let response = await fetch('/article/promise-chaining/user.json');,发送fetch到浏览器处理。第二个问题是 JS 引擎会等到 fetch 解决还是 JS 引擎会继续执行 let user = await response.json(); 以及 showAvatar 函数中的其余代码?如果是这样,由于没有收到响应,JS引擎如何处理 response.json() ?希望你明白我的意思)))。

【问题讨论】:

  • 在您的第一个示例中,承诺在计时器结束时被解决,因此浏览器等待计时器的持续时间,这解决了承诺,然后它才能执行.then() 中的回调。在您的第二个问题中,正如名称 await 所暗示的那样,它将暂停代码的执行,直到正在等待的承诺得到解决。由于浏览器在等待user.json响应,所以在收到响应之前不会执行下一行。
  • @Terry,嗨,感谢您的好心 cmets,在第一个示例中,如果 JS 引擎等到 promise 解决后再运行 then(),我认为它会阻止交互。关于第二个例子,如果 await 使函数停止,那么通过使用 async 我们将 showAvatar 函数推到调用堆栈之外,这样它就不会阻塞主堆栈,这是真的吗?
  • @MII - 它们都不会阻塞等待承诺解决的主要 JavaScript 线程。一旦引擎连接了then 处理程序,它就会继续做其他事情,直到计时器触发。 async 函数在每个 await 处暂停,允许线程做其他事情,并在等待的承诺解决时恢复。
  • @T.J.Crowder,关于 async/await 示例,当 async 函数在每个 await 处挂起时,async 函数是否会从调用堆栈中移除,并在等待 promise 时再次放回调用堆栈解决了吗?
  • @MII - 我在下面的答案中添加了一个答案。

标签: javascript


【解决方案1】:

你的第一个例子是这样工作的:

  1. new Promise 运行,同步调用你传递给它的函数(执行器函数
  2. 执行器函数中的代码调用setTimeout,传入一个1000ms后调用的函数;浏览器将其添加到其挂起的计时器回调列表中
  3. new Promise 返回承诺
  4. then 被调用,将传递给它的函数添加到 Promise 的履行处理程序列表中并创建一个新的 Promise(您的代码不使用它,因此它被丢弃)。
  5. 大约 1000 毫秒后,浏览器会排队调用 setTimeout 回调,JavaScript 引擎会接收并运行该回调
  6. 回调调用resolve 函数以履行价值1的承诺
  7. 这会触发 Promise 的实现处理程序(异步,但在本示例中并不重要),因此第 4 步中附加的处理程序被调用,显示 alert 然后返回 result * 2(即 @987654329 @,即1)。该值用于履行在第 4 步中创建和丢弃的承诺。

JS 引擎会等到 fetch 解决还是 JS 引擎会继续执行let user = await response.json();...

等待。 async 函数在await fetch(/*...*/) 中的await 处挂起,等待返回的promise fetch 解决。当它被挂起时,主 JavaScript 线程可以做其他事情。稍后,当 Promise 完成时,该函数将恢复,并且将履行值分配给 response(如果 Promise 被履行)或抛出异常(如果它被拒绝)。

更一般地说:async 函数在其代码中的第一个 awaitreturn 之前是同步的。到那时,他们返回他们的承诺,稍后根据async 函数的代码的其余部分来解决。


在你问的评论中:

当 async 函数在每次 await 时挂起时,是否会从调用堆栈中删除 async 函数,并在等待的 promise 完成时再次放回调用堆栈?

在低级别,是的;但是为了使调试更容易,一个好的、最新的 JavaScript 引擎会维护一个“异步调用堆栈”,它们用于错误跟踪等。例如,如果您在 Chrome 上运行它...

function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

async function inner() {
    await delay(80);
    throw new Error("boom");
}

async function outer() {
    await inner();
}

function wrapper() {
    outer()
    .then(result => {
        console.log(result);
    })
    .catch(error => {
        console.log(error.stack);
    });
}

wrapper();

...堆栈如下所示:

错误:繁荣 在内部(https://stacksn-ps.net/js:18:11) 在异步外部(https://stacksn-ps.net/js:22:5)

请注意“外部”之前的“异步”,还要注意wrapper 没有在任何地方提及。 wrapper 已完成并已返回,但 async 函数已暂停并恢复。

【讨论】:

  • FWIW,我在新书的第 8 章和第 9 章中深入探讨了 Promise 和 async 函数,请参阅我的个人资料以获取链接。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-08
  • 2020-11-11
  • 1970-01-01
  • 1970-01-01
  • 2016-05-16
  • 1970-01-01
相关资源
最近更新 更多