JS异步堆栈追踪之为什么await胜过Promise
作者:浅笑·
概述
async/await和Promise的根本区别在于await fn()暂停当前函数的执行,而promise.then(fn)在将fn调用添加到回调链后,继续执行当前函数。
const fn = () => console.log('hello') const a = async () => { await fn() // 暂停 fn 的执行 } // 调用 a 时,才恢复 fn 的执行 a() // "hello" const promise = Promise.resolve() // 将 fn 添加到回调链后,继续执行 fn promise.then(fn) // "hello"
在堆栈追踪的上下文中,这种差异非常显著。
当一个Promise链(无论是否脱糖化)在任何时候抛出一个未经处理的异常时,JavaScript引擎都会显示一条错误信息和(希望)记录一个有用的堆栈追踪。
作为一名开发人员,无论您使用的是普通的Promise还是async await,您都会期望这样。
Promise
想象一个场景,当对异步函数b的调用解析时,调用函数c:
const b = () => Promise.resolve() const a = () => { b().then(() => c()) }
当调用a时,将同步发生以下情况:
- b被调用并返回一个Promise,该Promise将在将来某个时刻解决。
- .then回调(实际上是调用c())被添加到回调链中( V8 术语中,[…]被添加为解析处理程序)。
之后,我们完成了在函数a的主体中执行代码。a永远不会被挂起,当对b的异步调用解析时,上下文已经消失了。
想象一下如果b(或c)异步抛出异常会发生什么?理想情况下,堆栈追踪应该包括a,因为b(或c)是从那里调用的,对吧?既然我们不在参考a了 ,那怎样能做到呢?
为了让它工作,JavaScript 引擎需要在上面的步骤之外做一些事情:它在有机会的时候捕获并存储堆栈追踪。
在V8中,堆栈追踪附加到b返回的Promise。当Promise实现时,堆栈追踪将被传递,以便c可以根据需要使用它。
b()[a] -> b().then()[a] -> c[a?:a]
捕获堆栈追踪需要时间(即降低性能);存储这些堆栈追踪需要内存。
async/await
下面是同样的程序,使用async/await而不是Promise编写:
const b = () => Promise.resolve() const a = async () => { await b() c() }
使用await,即使在await调用中不收集堆栈追踪,我们也可以恢复调用链。
这是可能的,因为a被挂起,正在等待b解决。如果b抛出异常,则可以按需以这种方式重建堆栈追踪。
如果c抛出异常,堆栈追踪可以像同步函数那样构造,因为发生这种情况时,我们仍在a上下文中。
通过遵循以下建议,使 JavaScript 引擎能够以更高效的方式处理堆栈追踪:
- 偏好async/await胜过Promise。
- 使用 @babel/preset env避免不必要的async/await传输。
以上就是JS异步堆栈追踪之为什么await胜过Promise的详细内容,更多关于Javascript的资料请关注脚本之家其它相关文章!