【译】Async/await和Promise的不为人知的秘密

4,065 阅读3分钟

大家知道,Async/await是generator和Promise的语法糖,但仅仅是语法糖吗? 它们两个的性能有没有区别呢, 又或者 promise.then()和await 同为微任务,但是它们的执行顺序是怎样的呢?

首先先介绍Async/await是如何优化JavaScript引擎的堆栈处理

是的,你没有看错,通过标题你就知道了,async/await相比较Promise来说,是优化了堆栈处理的,也就是说,在这一点上,Async/await是比Promise性能好的

与直接使用Promise相比,使用Async/Await不仅可以提高代码的可读性,同时也可以优化JavaScript引擎的执行方式

说完 ‘是什么’,接下来我们讨论一下 ‘为什么’

Async/Await与Promise最大区别在于:await b()会暂停所在的async函数的执行;而Promise.then(b)将b函数加入回调链中之后,会继续执行当前函数。对于堆栈来说,这个不同点非常关键。

当一个Promise链抛出一个未处理的错误时,无论我们使用await b()还是Promise.then(b),JavaScript引擎都需要打印错误信息及其堆栈。对于JavaScript引擎来说,两者获取堆栈的方式是不同的。

Promise.then()

观察下面代码, 假设b()返回一个promise

const a = () => {
    b().then(() => c())
}

当调用a()函数时,这些事情同步发生,b()函数产生一个promise对象,调用then方法,Promise会在将来的某个时刻resolve,也就是把then里的回调函数添加到回调链。(如果这一块不太明白,可以仔细学习promise,或者读一读promise源码并尝试写一写,相信你更通透),这样,a()函数就执行完了,在这个过程中,a()函数并不会暂停,因此在异步函数resolve的时候,a()的作用域已经不存在了,那要如何生成包含a()的堆栈信息呢? 为了解决这个问题,JavaScripts引擎要做一些额外的工作;它会及时记录并保存堆栈信息。对于V8引擎来说,这些堆栈信息随着Promise在Promise链中传递,这样c()函数在需要的时候也能获取堆栈信息。但是这无疑造成了额外的开销,会降低性能;保存堆栈信息会占用额外的内存。

Await

我们可以用Async/await来实现一下

const a = () => {
    await b()
    c()
}

使用await的时候,无需存储堆栈信息,因为存储b()到a()的指针的足够了。当b()函数执行的时候,a()函数被暂停了,因此a()函数的作用域还在内存可以访问。如果b()抛出一个错误,堆栈通过指针迅速生成。如果c()函数抛出一个错误,堆栈信息也可以像同步函数一样生成,因为c()是在a()中执行的。不论是b()还是c(),我们都不需要去存储堆栈信息,因为堆栈信息可以在需要的时候立即生成。而存储指针,显然比存储堆栈更加节省内存

结论

很多ECMAScript语法特性看起来都只是些语法糖,其实并非如此,至少Async/await绝不仅仅是语法糖 为了让JavaScript引擎处理堆栈的方式性能更高,请尽量使用Async/await,而不是直接使用Promise

tip: 文章开头所说的执行顺序,请移步查看下一篇文章~