zoukankan      html  css  js  c++  java
  • JavaScript await 与 promise 的纠葛

    大概在去年的这个时候,V8 团队发布了一篇博文Faster async functions and promises,向我们介绍了他们是如何提升 await 的执行速度,值得一看,这里还有中文版。没有这个前提,看我的这篇文章可能就没啥意义了。

    博文中提到了在不同 Node 版本中,await 和 promise 执行时机不同的问题,如下:

    可以说,这种“正确的行为”其实并不直观,对 JavaScript 开发者来说实际上是令人惊讶的,所以值得做一些解释。

    博文由浅入深讲解了 await 的底层原理和优化过程,说实话,看一遍可能很懵逼。譬如,试着解答下面代码【PP】的执行顺序,以及为什么会这样?

    const p = Promise.resolve(42)
    
    const asyncFn = (async function () {
      await p; 
      console.log('after:await', 1);
      await p;
      console.log('after:await', 2);
    })();
    
    asyncFn.then(() => console.log('after:asyncFn'))
    
    Promise.resolve(p).then(() => console.log('tick:1'))
     .then(() => console.log('tick:2'))
     .then(() => console.log('tick:3'))
     .then(() => console.log('tick:4')) 
     .then(() => console.log('tick:5')) 
     .then(() => console.log('tick:6')) 
     .then(() => console.log('tick:7')) 
     .then(() => console.log('tick:8')) 
     .then(() => console.log('tick:9')) 
    

    所以,今天我尝试结合 V8 团队的文章和 ecma262 await 规范,看看能否直白的说清楚这个过程。Node 版本为 v10.16.3。

    规范 6.2.3.1Await

    对于 await (value),按以下规范实现:

    1. asyncContext 作为当前的执行上下文
    2. promise = PromiseResolve(%Promise%, value).
    3. 定义 stepsFulfilled,它的执行步骤定义在 Await Fulfilled Functions.
    4. onFulfilled = CreateBuiltinFunction(stepsFulfilled, « [[AsyncContext]] »).
    5. onFulfilled.[[AsyncContext]] = asyncContext.
    6. 定义 stepsRejected,它的执行步骤定义在Await Rejected Functions.
    7. onRejected = CreateBuiltinFunction(stepsRejected, « [[AsyncContext]] »).
    8. onRejected.[[AsyncContext]] = asyncContext.
    9. 执行 PerformPromiseThen(promise, onFulfilled, onRejected).
    10. 将 asyncContext 从执行上下文栈顶移除
    11. 设置 asyncContext 代码的运行状态,以便在未来恢复运行后,await 之后的代码能够被完全执行。
    12. return 一个 promise

    await 的 V8 表示

    假设我们有这样的代码:

    async function foo(v) {
      const w = await v;
      return w;
    }
    

    V8 将其翻译成这样:

    resumable function foo(v) {
      implicit_promise = createPromise();
    
      // 将 v 包装成一个 promise
      vPromise = promiseResolve(v)
    
      // 为恢复 foo 函数添加处理器
      throwaway = createPromise();
      performPromiseThen(vPromise,
        res => resume(<<foo>>, res),
        err => throw(<<foo>>, err),
        throwaway);
    
      // 中断 foo,返回一个 implicit_promise
      w = suspend(<<foo>>, implicit_promise);
      return resolvePromise(implicit_promise, w);
    }
    
    function promiseResolve(v) {
      if (v is Promise) return v;
      promise = createPromise();
      resolvePromise(promise, v);
      return promise;
    }
    

    performPromiseThen

    以上代码的关键,就是 performPromiseThen,在本例中,可以粗浅的理解为:执行 performPromiseThen,会在 microtask 中入队一个PromiseReactionJob,这个 job 做两件事——对将要到来的值运用合适的 handler,然后用 handler 的返回值去 resolve/reject 与该 handler 相关的派生 promise。

    看起来有点迷!

    其实在本例中,handler 对应 res => resume(<<foo>>, res),派生的 promise 对应throwaway,介此,我们可以用下面的伪代码表示这一行为:

    Promise.resolve(value)
      .then(res => resume(<<foo>>, res))
      .then(res => throwaway(res))
    

    好了,不妨用楼上的代码【PP】,动动手,试着模拟下 microtask 队列中的 job 变化。

    代码第一次执行完毕,microtask = [PromiseReactionJob,tick:1],此时无任何输出;

    执行 PromiseReactionJob,microtask = [tick:1,resume],此时无输出;

    执行 tick:1,microtask = [resume,tick:2],打印tick:1;

    执行 resume,microtask = [tick:2,throwaway],此时无输出;

    执行 tick:2,microtask = [throwaway,tick:3],打印tick:2;

    执行 throwaway,microtask = [tick:3,PromiseReactionJob],打印'after:await', 1;

    ....

    不断地重复以上操作,直到 microtask 为空。

    所以,最后的打印结果为:

    tick:1
    tick:2
    after:await 1
    tick:3
    tick:4
    tick:5
    after:await 2
    tick:6
    after:asyncFn
    tick:7
    tick:8
    tick:9
    

    最后的思考

    了解了 performPromiseThen 的执行过程,你或许会有一个疑问:throwaway 这个内部 promise 好像没啥用?对,V8 团队也是这么想的,他们向 ecma262 提了一个 issue Spec factoring: allow PerformPromiseThen with no capability

    另外,在代码片段【PP】中,如果把const p = Promise.resolve(42)换成const p = 42,会有一种 holy shit 的感觉,你可以试试,然后麻烦回来告诉我原因,感谢~!

  • 相关阅读:
    进程池线程池
    线程与其操作方法
    生产者消费者模型
    Java反射机制详解
    ajax跨域原理以及解决方案
    数据库连接池的选择 Druid
    新目标
    让webstorm支持avalon语法自动补全
    使用IDEA和gradle搭建Spring MVC和MyBatis开发环境
    使用IDEA自带的rest client参数传递乱码问题
  • 原文地址:https://www.cnblogs.com/fayin/p/12106019.html
Copyright © 2011-2022 走看看