与直接使用 Promise
相比,使用 async/await
不只能够使代码更具可读性,并且还能够在 JavaScript 引擎中实现一些有趣的优化。html
这篇文章是关于一个这样的优化,涉及异步代码的堆栈追踪。promise
async/await
和 Promise
的根本区别在于 await fn()
暂停当前函数的执行,而 promise.then(fn)
在将 fn
调用添加到回调链后,继续执行当前函数。babel
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 引擎都会显示一条错误信息和(但愿)记录一个有用的堆栈追踪。async
做为一名开发人员,不管您使用的是普通的 Promise
仍是 async await
,您都会指望这样。函数
想象一个场景,当对异步函数 b
的调用解析时,调用函数 c
:性能
const b = () => Promise.resolve() const a = () => { b().then(() => c()) }
当调用 a
时,将同步发生如下状况:优化
b
被调用并返回一个 Promise
,该 Promise
将在未来某个时刻解决。.then
回调(其实是调用 c()
)被添加到回调链中( V8 术语中,[…]被添加为解析处理程序)。以后,咱们完成了在函数 a
的主体中执行代码。a
永远不会被挂起,当对 b
的异步调用解析时,上下文已经消失了。code
想象一下若是 b
(或 c
)异步抛出异常会发生什么?理想状况下,堆栈追踪应该包括 a
,由于 b
(或 c
)是从那里调用的,对吧?既然咱们不在参考 a
了 ,那怎样能作到呢?htm
为了让它工做,JavaScript 引擎须要在上面的步骤以外作一些事情:它在有机会的时候捕获并存储堆栈追踪。
在 V8 中,堆栈追踪附加到 b
返回的 Promise
。当 Promise
实现时,堆栈追踪将被传递,以便 c
能够根据须要使用它。
b()[a] -> b().then()[a] -> c[a?:a]
捕获堆栈追踪须要时间(即下降性能);存储这些堆栈追踪须要内存。
下面是一样的程序,使用 async/await
而不是 Promise
编写:
const b = () => Promise.resolve() const a = async () => { await b() c() }
使用 await
,即便在 await
调用中不收集堆栈追踪,咱们也能够恢复调用链。
这是可能的,由于 a
被挂起,正在等待 b
解决。若是 b
抛出异常,则能够按需以这种方式重建堆栈追踪。
若是 c
抛出异常,堆栈追踪能够像同步函数那样构造,由于发生这种状况时,咱们仍在 a
上下文中。
经过遵循如下建议,使 JavaScript 引擎可以以更高效的方式处理堆栈追踪:
async/await
赛过 Promise
。async/await
传输。