首页 > 解决方案 > 使用 async/await 时调用堆栈的工作

问题描述

使用 async/await 函数时调用堆栈的行为如何?


function resolveAfter2Seconds() { // taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('resolved');
    }, 2000);
  });
}

const asyncFuntion=async()=>{
  const result = await resolveAfter2Seconds();
  console.info("asyncFuntion finish, result is: ", result);
}

const first = async()=>{
    await asyncFuntion();
    console.log('first completed');
        debugger;
}

const second = ()=>{
    console.log('second completed');
        debugger;
}

function main(){
    first();
    second();
}


main();

在上面的代码中,当在 second() 中遇到第一个断点时,我可以看到调用堆栈包含 main() 和 second()。在 first() 中的第二个断点期间,调用堆栈包含 main() 和 first()。

在第一个断点期间 first() 发生了什么。推到哪里去了?假设 asyncFunction() 需要一些时间才能完成。

有人请帮忙。

标签: javascriptecmascript-6promiseevent-loop

解决方案


首先,当你到达你点击的断点时secondfirst已经执行并且不再在堆栈上。

当我们进入时first,我们立即击中了一个await asyncFunction()。这告诉 JavaScript 不要阻塞调用的结果,但在我们等待的时候可以随意寻找其他事情要做。Javascript 是做什么的?

嗯,首先,它确实调用了asyncFunction(). 这将返回一个承诺,并将启动一些稍后将解决它的异步过程。现在我们不能继续下一行first(即 the console.log('first completed')),因为我们的await意思是在承诺完成之前我们不能继续,所以我们需要在这里暂停执行,然后找点空闲时间做点别的事情。

所以,我们查找堆栈。我们仍然在first()来自 main 的调用中,现在我们可以从该调用中返回一个 Promise,我们将在异步执行完成时解决它。main忽略该承诺返回值,因此我们继续使用second(). 一旦我们执行完毕second,我们就回顾一下所谓的那个,并以每个人都期望的方式继续同步执行。

然后,在未来的某个时刻,我们的承诺实现了。也许它正在等待 API 调用返回。也许它正在等待数据库回复它。在我们的示例中,它正在等待 2 秒超时。无论它在等待什么,现在都可以处理了,我们可以取消暂停first调用并继续在那里执行。它不是从内部“调用”的main——函数在内部被拾取,就像回调一样,但关键是用一个全新的堆栈调用,一旦我们完成调用函数的其余部分,它将被销毁。

鉴于我们在一个新堆栈中,并且早已离开“主”堆栈框架,当我们在其中遇到断点时,如何main再次first进入堆栈?

很长一段时间以来,如果您在调试器中运行代码,简单的答案是它们不会。您只需获得您所在的函数,调试器就会告诉您它是从“异步代码”或类似代码中调用的。

然而,现在一些调试器可以跟随等待的代码回到它正在解决的承诺(请记住,await并且async大部分只是承诺之上的语法糖)换句话说,当您等待的代码完成并且引擎盖下的“承诺”得到解决时,您的调试器有助于确定堆栈“应该”是什么样子。它显示的内容实际上与引擎最终调用该函数的方式并无太大相似之处——毕竟,它是在事件循环之外调用的。但是,我认为这是一个有用的补充,使我们所有人都能够保持代码的思维模型比实际发生的要简单得多!

一些关于它是如何工作的进一步阅读,其中包含比我在这里更多的细节:


推荐阅读