资讯专栏INFORMATION COLUMN

从JS引擎理解Await b()与Promise.then(b)的堆栈处理

ziwenxie / 2915人阅读

摘要:对于引擎来说,两者获取堆栈的方式是不同的。对于引擎来说,堆栈信息附加在了函数所返回的并在链中传递,这样函数也能在需要的时候获取堆栈信息。使用可以实时监控线上应用的错误,并获取完整的堆栈信息。

译者按: Async/Await真的只是简单的语法糖吗?No!

原文:Asynchronous stack traces: why await beats .then()

作者: Mathias Bynens: Google V8引擎开发者

译者:Fundebug

为了保证可读性,本文采用意译而非直译。另外,本文版权归原作者所有,翻译仅用于学习。

与直接使用Promise相比,使用Async/Await不仅可以提高代码的可读性,同时也可以优化JavaScript引擎的执行方式。这篇博客将介绍Async/Await是如何优化JavaScript引擎对堆栈信息的处理。

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

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

Promise.then(b)

示例代码中,函数c()会在异步函数b()成功resolve之后执行:

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

当调用a()函数时,这些事情同步发生:

b()函数被调用,它会返回一个Promise,这个Promise会在未来的某个时刻resolve。

.then()回调函数(实际调用了c()函数)被添加到回调链。

这样,a()函数内的代码就执行完了。a()函数不会被暂停,因此在异步函数b()resolve时,a()函数的作用域已经不存在了。假设b()或者c()抛出了一个错误,则堆栈信息中应该包含a()函数,因为它们都是在a()函数内被调用。对a()函数的任何引用都不存在了,要如何生成包含a()的堆栈信息呢?

为了解决这个问题,JavaScript引擎需要做一些额外的工作:它会及时记录并且保存堆栈信息。对于V8引擎来说,堆栈信息附加在了b()函数所返回的Promise并在Promise链中传递,这样c()函数也能在需要的时候获取堆栈信息。

记录堆栈信息需要时间,这样会降低性能;而保存堆栈信息需要占用额外的内存。

使用Fundebug, 可以实时监控线上应用的错误,并获取完整的堆栈信息。

Await b()

我们可以使用Async/Await实现同样的代码,同步函数c()会等到异步函数b()执行结束之后再执行:

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

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

建议

很多ECMAScript语法特性看起来都只是语法糖,其实并非如此,至少Async/Await绝不仅仅只是语法糖。

为了让JavaScript引擎处理堆栈的方式性能更高并且更加节省内存,请遵循这些建议:

使用Async/Await,而不是直接使用Promise

使用babel-preset-env,避免Babel不必要地转换Async/Await

尽管V8引擎还没有实现这些优化,请遵循这些建议。当我们为V8实现这些优化的时候,你的程序可以保证最佳的性能。(作者是V8引擎的开发者)

一般来说,尽量不要去使用Babel转码器。所有支持Service Workers的浏览器都支持Async/Await,因此没有必要去对Async/Await转码。这一点对于JavaScript modules via script tag同样适用。关于这一点,大家可以参考Deploying ES2015+ Code in Production Today。

关于Fundebug

Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了6亿+错误事件,得到了Google、360、金山软件等众多知名用户的认可。欢迎免费试用!

版权声明

转载时请注明作者Fundebug以及本文地址:
https://blog.fundebug.com/2018/07/18/javascript-engine-await-promise/

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/96706.html

相关文章

  • JavaScript是如何工作:事件循环和异步编程崛起+ 5种使用 async/await

    摘要:事件循环从回调队列中获取并将其推入调用堆栈。执行从调用堆栈中移除从调用堆栈中移除快速回顾值得注意的是,指定了事件循环应该如何工作,这意味着在技术上它属于引擎的职责范围,不再仅仅扮演宿主环境的角色。 此篇是 JavaScript是如何工作的第四篇,其它三篇可以看这里: JavaScript是如何工作的:引擎,运行时和调用堆栈的概述! JavaScript是如何工作的:深入V8引擎&编写...

    Honwhy 评论0 收藏0
  • JavaScript 工作原理之四-事件循环及异步编程出现和 5 种更好 async/await

    摘要:函数会在之后的某个时刻触发事件定时器。事件循环中的这样一次遍历被称为一个。执行完毕并出栈。当定时器过期,宿主环境会把回调函数添加至事件循环队列中,然后,在未来的某个取出并执行该事件。 原文请查阅这里,略有改动。 本系列持续更新中,Github 地址请查阅这里。 这是 JavaScript 工作原理的第四章。 现在,我们将会通过回顾单线程环境下编程的弊端及如何克服这些困难以创建令人惊叹...

    maochunguang 评论0 收藏0
  • 前端面试题 -- JavaScript(二)

    摘要:抛出的错误对象会被方法回调函数接收到命令命令后面是一个对象,返回该对象的结果。有人将其称之为宏任务微任务,定时器就属于宏任务的范畴。 前言 上一篇 前端面试题-JavaScript(一), 感兴趣的小伙伴也可以移步这里查看 完整版JavaScript面试题,面试题会不定期更新加进去一些个人工作中遇到的或者认为比较重要的东西,后面会涉及到前端的各个方面,感兴趣的小伙伴可以关注哦! 如果文...

    cgspine 评论0 收藏0
  • 最后一次搞懂 Event Loop

    摘要:由于是单线程的,这些方法就会按顺序被排列在一个单独的地方,这个地方就是所谓执行栈。事件队列每次仅执行一个任务,在该任务执行完毕之后,再执行下一个任务。 Event Loop 是 JavaScript 异步编程的核心思想,也是前端进阶必须跨越的一关。同时,它又是面试的必考点,特别是在 Promise 出现之后,各种各样的面试题层出不穷,花样百出。这篇文章从现实生活中的例子入手,让你彻底理解 E...

    gself 评论0 收藏0

发表评论

0条评论

ziwenxie

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<