专栏名称: 前端外刊评论
最新、最前沿的前端资讯,最有深入、最干前端相关的技术译文。
目录
相关文章推荐
前端早读课  ·  【第3453期】圈复杂度在转转前端质量体系中的应用 ·  21 小时前  
前端早读课  ·  【第3452期】React 开发中使用开闭原则 ·  昨天  
前端早读课  ·  【第3451期】前端 TypeError ... ·  2 天前  
51好读  ›  专栏  ›  前端外刊评论

v8是怎么实现更快的 await ?

前端外刊评论  · 公众号  · 前端  · 2019-03-11 07:00

正文

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

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

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

Example

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

  1. const p = Promise.resolve();


  2. (async () => {

  3. await p;

  4. console.log("after:await");

  5. })();


  6. p.then(() => {

  7. console.log("tick:a");

  8. }).then(() => {

  9. console .log("tick:b");

  10. });

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

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

  1. tick:a

  2. tick:b

  3. after:await

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

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

将 await 翻译成 promise

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

我们可以用 promise 语法写成:

  1. function foo2(v) {

  2. const implicit_promise = new Promise(resolve => {

  3. const promise = new Promise(res => res(v));

  4. promise.then(w => resolve(w));

  5. });


  6. return implicit_promise;

  7. }

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

  1. const p = Promise.resolve();


  2. (() => {

  3. const implicit_promise = new Promise(resolve => {

  4. const promise = new Promise(res => res(p));

  5. promise .then(() => {

  6. console.log("after:await");

  7. resolve();

  8. });

  9. });


  10. return implicit_promise;

  11. })();


  12. p.then(() => {

  13. console.log("tick:a");

  14. }).then(() => {

  15. console.log("tick:b");

  16. });

经过一些琐碎的调试,发现问题真正的关键代码是这一句: 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 情况非常不同,用伪代码表示则是:

  1. addToMicroTaskQueue(() => { // 任务A

  2. // 使用 .then 方法,将 promiseA 的状态 和 p 绑定

  3. p.then(

  4. resolvePromiseA, // 任务B

  5. rejectPromiseA

  6. );

  7. });

我们一步一步来分析:

  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

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

01月28日更新,之前微任务队列里面的任务没有考虑顺序,这里做一下修改,以下队列里的任务是顺序有关,从左往右,左边的先执行

1、当代码执行完后

  • MicroTask







请到「今天看啥」查看全文