最近v8团队发表一篇博客Faster async functions and promises, 预计在v7.2版本实现更快的异步函数和promise

文章内容看起来不是很容易理解,背后的原理比较隐蔽,不过博客提到的一些ECMAScript 标准文档中的操作、任务,实际上都有已经实现的 built-in api, 因此我们可以借助我们比较熟悉的语法、api 来理解其中的原理,

也许本文有些说法不够准确,欢迎纠正

Example

首先看下博客开篇提到的代码:

const p = Promise.resolve();

(async () => {
await p;
console.log("after:await");
})(); p.then(() => {
console.log("tick:a");
}).then(() => {
console.log("tick:b");
});

以 node v10 的执行结果为准,node v8 的实现是不符合ECMAScript 标准

优秀的程序员总是能以简单的例子解释复杂的原理。代码很简单,但是执行结果可能出乎很多人意料:

tick:a
tick:b
after:await

如果你已经猜对了,本文的关键内容你已经掌握,不用往下看了:)。

为什么 after:await会出现在tick:a之后,甚至是tick:b之后? 要理解其中的原理,我们可以做一个小实验。

将 await 翻译成 promise

v8博客中是以伪代码的方式解释await的执行逻辑:

原图 https://v8.dev/_img/fast-async/await-under-the-hood.svg

我们可以用promise语法写成:

function foo2(v) {
const implicit_promise = new Promise(resolve => {
const promise = new Promise(res => res(v));
promise.then(w => resolve(w));
}); return implicit_promise;
}

按照同样的方式,可以将文章开头的代码转换成:

const p = Promise.resolve();

(() => {
const implicit_promise = new Promise(resolve => {
const promise = new Promise(res => res(p));
promise.then(() => {
console.log("after:await");
resolve();
});
}); return implicit_promise;
})(); p.then(() => {
console.log("tick:a");
}).then(() => {
console.log("tick:b");
});

经过一些琐碎的调试,发现问题真正的关键代码是这一句: const promise = new Promise(res => res(p));

Resolved with another promise

了解 Node.js 或浏览器的事件循环的童鞋都知道,resolved promise 的回调函数(reaction)是放在一个单独的队列MicroTask Queue中。 这个队列会在事件循环的阶段结束的时候被执行,只有当这个队列被清空后,才能进入事件循环的下一个阶段。

我们知道一个 promise 的 .then 回调的返回值可以是一个任意值,也可以是另外一个 promise。 但是后者的处理逻辑可能有点反直觉

在深入之前,我们简单说一下 promise 的几种状态:

  • 我们说一个 promise 是 resolved 的,表示它不能被再次 fulfill 或 reject, 要么是被 fulfill,要么被 reject(这两种情况,promise 均有一个确定的 non-promise result), 要么遵循另外一个 promise(随之 fulfill 或 reject)
  • 我们说一个 promise 是 unresolved 的,表示它尚未被 resolve

当一个 promise(假设叫 promiseA。方便引用) 被 resolve,并且去遵循另外一个 promise(叫 p) 时,执行逻辑和前面两种 resolve 情况非常不同,用伪代码表示则是:

addToMicroTaskQueue(() => { // 任务A
// 使用 .then 方法,将 promiseA 的状态 和 p 绑定
p.then(
resolvePromiseA, // 任务B
rejectPromiseA
);
});

我们一步一步来分析:

  1. 首先,我们在MicroTask Queue添加任务A,该任务在 ECMAScript 标准 中被定义为 PromiseResolveThenableJob
  2. 任务A,主要目的是使 promiseA 遵循 p 的状态,将两者的状态关联起来。
  3. 由于我们例子中 p 已经是 resolved(状态为fulfilled)的,所以立即将resolvePromiseA任务B 添加到MicroTask Queue
  4. 在 resolvePromiseA 执行后,promiseA 才是 resolved (状态为 fulfilled,值为 p 的 fulfilled value)

我们可以看到,从 new Promise(res=>res(p)) 到该调用返回的 promise 真正被 resolve 至少需要两次microtick——在我们的例子中,是遍历了两次 MicroTask Queue

这个时候,我们终于可以理清楚开头代码的执行顺序:

1、当代码执行完后

    • MicroTask Queue有两个任务:tick:aPromiseResolveThenableJob

2、开始执行 runMicrotasks()

    • MicroTask Queue变成:tick:bresolvePromiseA
    • console: tick:a

3、MicroTask Queue没有清空,继续执行队列中的任务

    • MicroTask Queue变成:after:await
    • console: tick:a, tick:b

4、继续执行,清空MicroTaak Queue

    • console: tick:a, tick:b, after:await

未来更快的 v8

借助我们更熟悉的promise,我们基本知道了现阶段的await的执行机制,这样我们就能很好理解为什么 v8 博客中提到的改进可以使 await 执行更快:

将 new Promise(res=>res(p)) 替换成 Promise.resolve(p)

根据MDN文档, 当 p 是一个 promise 时,Promise.resolve(p)直接返回 p,而这是大概率事件。

因此,我们减少了 promise 之间状态同步需要的两次 microtick,那样,上述代码的输出结果就是:

after:await
tick:a
tick:b
编辑于 2019-01-04

v8是怎么实现更快的 await ?深入理解 await 的运行机制的更多相关文章

  1. 让DB2跑得更快——DB2内部解析与性能优化

    让DB2跑得更快——DB2内部解析与性能优化 (DB2数据库领域的精彩强音,DB2技巧精髓的热心分享,资深数据库专家牛新庄.干毅民.成孜论.唐志刚联袂推荐!)  洪烨著 2013年10月出版 定价:7 ...

  2. 让你的 Node.js 应用跑得更快的 10 个技巧(转)

    Node.js 受益于它的事件驱动和异步的特征,已经很快了.但是,在现代网络中只是快是不行的.如果你打算用 Node.js 开发你的下一个Web 应用的话,那么你就应该无所不用其极,让你的应用更快,异 ...

  3. 让Python代码更快运行的 5 种方法

    不论什么语言,我们都需要注意性能优化问题,提高执行效率.选择了脚本语言就要忍受其速度,这句话在某种程度上说明了Python作为脚本语言的不足之处,那就是执行效率和性能不够亮.尽管Python从未如C和 ...

  4. Java8 更快的原子类:LongAdder(笔记)

    更快的原子类:LongAdder      大家对AtomicInteger的基本实现机制应该比较了解,它们是在一个死循环内,不断尝试修改目标值,知道修改成功,如果竞争不激烈,那么修改成功的概率就很高 ...

  5. 精通Web Analytics 2.0 (9) 第七章:失败更快:爆发测试与实验的能量

    精通Web Analytics 2.0 : 用户中心科学与在线统计艺术 第七章:失败更快:爆发测试与实验的能量 欢迎来到实验和测试这个棒极了的世界! 如果Web拥有一个超越所有其他渠道的巨大优势,它就 ...

  6. 假如 UNION ALL 里面的子句 有 JOIN ,那个执行更快呢

    比如: select id, name from table1 where name = 'x' union all select id, name from table2 where name =  ...

  7. 【译】更快的方式实现PHP数组去重

    原文:Faster Alternative to PHP’s Array Unique Function 概述 使用PHP的array_unique()函数允许你传递一个数组,然后移除重复的值,返回一 ...

  8. ubuntu 12.04 LTS 如何使用更快的更新源

    装好ubuntu系统后的第一见事就是替换自带的更新源,原因是系统自带的源有些在中国访问不了,可以访问的速度又特别慢.幸好国内的一些公司和大学提供了速度不错的更新源.下面介绍如何使用更快的更新源 方法/ ...

  9. php提供更快的文件下载

    在微博上偶然看到一篇介绍php更快下载文件的方法,其实就是利用web服务器的xsendfile特性,鸟哥的博客中只说了apache的实现方式,我找到了介绍nginx实现方式的文章,整理一下! let' ...

随机推荐

  1. 基础004_V7-DSP Slice

    主要参考ug479.pdf.之前的文章:FIR调用DSP48E_05.本文主要记录基本用法. 一.DSP48核 A-参数说明 instrctions,多个功能,通过sel选用 目前没发现C勾选与否,有 ...

  2. pbr若干概念

    pbr基于辐射传输理论,最基本的一个观点是:一切皆光源--任何一个面元既是光能接收器,也是光能发射器. 光通(flux):单位时间内通过某一面积的光能,单位W(瓦特),用表示. 可见,光通其实就是功率 ...

  3. javaweb可部署目录结构

    webApp //项目名称 -META-INF --MANIFEST.MF -WEB-INF --classes   //编译class文件 --lib  //依赖jar --web.xml -ind ...

  4. HTC T329手机如何删除系统自带的软件?HTC一键解锁、获取ROOT权限、豌豆荚删除系统软件

    手头一部HTC T329T手机,机上默认装载的软件实在太多了,居然占用了4页.用360手机卫士并不能删除系统软件(不能获取ROOT权限).查网上查询,总结要删除系统软件步骤如下(本人不刷机,只是想删除 ...

  5. Atitti html5 h5 新特性attilax总结

    Atitti html5 h5 新特性attilax总结 Attilax觉得不错的新特性 3.语义Header和Footer (The Semantic Header and Footer) 8.占位 ...

  6. Flink KAFKA

    https://data-artisans.com/blog/kafka-flink-a-practical-how-to https://github.com/dataArtisans/kafka- ...

  7. [AWS vs Azure] 云计算里AWS和Azure的探究(5) ——EC2和Azure VM磁盘性能分析

    云计算里AWS和Azure的探究(5) ——EC2和Azure VM磁盘性能分析 在虚拟机创建完成之后,CPU和内存的配置等等基本上是一目了然的.如果不考虑显卡性能,一台机器最重要的性能瓶颈就是硬盘. ...

  8. 每日英语:How to Solve India's Huge Food Wastage

    India is one of the world’s  largest producers of fruits and vegetables, but a third of its produce ...

  9. 每日英语:South India's Streetside Coffee Culture

    Early one morning last week I queued outside Sri Gopi Iyengar Coffee and Tiffin Center, a coffee bar ...

  10. 网页安装ipa

    在网页上直接下载并安装ipa,兼容所欲iso,包含没有越狱的 1.html代码 <a href="itms-services://?action=download-manifest&a ...