使用 async/await 时调用堆栈的工作

Zee*_*een 8 javascript event-loop promise ecmascript-6

使用 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();

Run Code Online (Sandbox Code Playgroud)

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

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

有人请帮忙。

Hec*_*ksa 9

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

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

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

所以,我们查找堆栈。我们仍然在first()main的调用中,现在我们可以从那个调用中返回一个 promise,当异步执行完成时我们将解析它。main忽略那个承诺返回值,所以我们继续使用second(). 一旦我们执行了second,我们回顾一下调用它的任何东西,并以每个人都期望的方式继续同步执行。

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

鉴于我们在一个新的堆栈,并早已离开了“主”堆栈帧,怎么办main,并first再次结束了在栈上,当我们打到里面的断点?

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

然而,时下一些调试器可以按照已久的代码返回的承诺,它是解决(记住,await并且async多是对承诺的顶部只是语法糖)。换句话说,当您等待的代码完成并且引擎盖下的“承诺”解决时,您的调试器会帮助确定堆栈“应该”是什么样子。它显示的内容实际上与引擎最终调用该函数的方式没有太多相似之处——毕竟,它是从事件循环中调用的。然而,我认为这是一个有用的补充,使我们所有人都能保持我们代码的心理模型比实际发生的更简单!

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