• Promise杂记


    更好的阅度体验

    • 前言
    • API
    • Promise特点
    • 状态跟随
    • V8中的async await和Promise
    • 实现一个Promise
    • 参考

    前言

    作为一个前端开发,使用了Promise一年多了,一直以来都停留在API的调用阶段,没有很好的去深入。刚好最近阅读了V8团队的一篇如何实现更快的async await,借着这个机会整理了Promise的相关理解。文中如有错误,请轻喷~

    API

    Promise是社区中对于异步的一种解决方案,相对于回调函数和事件机制更直观和容易理解。ES6 将其写进了语言标准,统一了用法,提供了原生的Promise对象。

    这里只对API的一些特点做记录,如果需要详细教程,推荐阮老师的Promise对象一文

    new Promise
    --创建一个promise实例

    Promise.prototype.then(resolve, reject)
    --then方法返回一个新的Promise实例

    Promise.prototype.catch(error)
    --.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
    --错误会一直传递,直到被catch,如果没有catch,则没有任何反应
    --catch返回一个新的Promise实例

    Promise.prototype.finally()
    --指定不管 Promise 对象最后状态如何,都会执行的操作。
    --实现如下:

    Promise.prototype.finally = function (callback) {
      let P = this.constructor;
      return this.then(
        value  => P.resolve(callback()).then(() => value),
        reason => P.resolve(callback()).then(() => { throw reason })
      );
    };
    

    Promise.all([promise Array])
    --将多个 Promise 实例,包装成一个新的 Promise 实例
    --所有子promise执行完成后,才执行all的resolve,参数为所有子promise返回的数组
    --某个子promise出错时,执行all的reject,参数为第一个被reject的实例的返回值
    --某个子promise自己catch时,不会传递reject给all,因为catch重新返回一个promise实例

    Promise.race([promise Array])
    --将多个 Promise 实例,包装成一个新的 Promise 实例。
    --子promise有一个实例率先改变状态,race的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给race的回调函数。

    Promise.resolve()
    --将现有对象转为 Promise 对象
    --参数是promise实例, 原封不动的返回
    --参数是一个thenable对象 将这个对象转为 Promise 对象,状态为resolved
    --参数是一个原始值 返回一个新的 Promise 对象,状态为resolved
    --不带有任何参数 返回一个resolved状态的 Promise 对象。
    --等价于如下代码

    Promise.resolve('foo')
    // 等价于
    new Promise(resolve => resolve('foo'))
    

    Promise.reject()
    --返回一个新的 Promise 实例,该实例的状态为rejected
    --Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。

    Promise特点

    很多文章都是把resolve当成fulfilled,本文也是,但本文还有另外一个resolved,指的是该Promise已经被处理,注意两者的区别  
    
    1. 对象具有三个状态,分别是pending(进行中)、fulfilled(resolve)(已成功)、reject(已失败),并且对象的状态不受外界改变,只能从pending到fulfilled或者pending到reject。  
    
    2. 一旦状态被改变,就不会再变,任何时候都能得到这个结果,与事件回调不同,事件回调在事件过去后无法再调用函数。  
    
    3. 一个promise一旦resolved,再次resolve/reject将失效。即只能resolved一次。  
    
    4. 值穿透,传给then或者catch的参数为非函数时,会发生穿透(下面有示例代码)  
    
    5. 无法取消,Promise一旦运行,无法取消。  
    
    6. 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部  
    
    7. 处于pending时,无法感知promise的状态(刚刚开始还是即将完成)。
    

    值穿透代码:

    new Promise(resolve=>resolve(8))
      .then()
      .then()
      .then(function foo(value) {
        console.log(value)  // 8
      })
    

    状态追随

    状态追随的概念和下面的v8处理asyac await相关联

    状态跟随就是指将一个promise(代指A)当成另外一个promise(代指B)的resolve参数,即B的状态会追随A。
    如下代码所示:

    const promiseA = new Promise((resolve) => {
      setTimeout(() => {
        resolve('ccc')
      }, 3000)
    })
    const promiseB = new Promise(res => {
      res(promiseA)
    })
    promiseB.then((arg) => {
      console.log(arg) // print 'ccc' after 3000ms
    })
    

    按理说,promiseB应该是已经处于resolve的状态, 但是依然要3000ms后才打印出我们想要的值, 这难免让人困惑

    在ES的标准文档中有这么一句话可以帮助我们理解:

    A resolved promise may be pending, fulfilled or rejected.

    就是说一个已经处理的promise,他的状态有可能是pending, fulfilled 或者 rejected。 这与我们前面学的不一样啊, resolved了的promise不应该是处于结果状态吗?这确实有点反直觉,结合上面的例子看,当处于状态跟随时,即使promiseB立即被resolved了,但是因为他追随了promiseA的状态,而A的状态则是pending,所以才说处于resolved的promiseB的状态是pending。

    再看另外一个例子:

    const promiseA = new Promise((resolve) => {
      setTimeout(() => {
        resolve('ccc')
      }, 3000)
    })
    const promiseB = new Promise(res => {
      setTimeout(() => {
        res(promiseA)
      }, 5000)
    })
    promiseB.then((arg) => {
      console.log(arg) // print 'ccc' after 5000ms
    })
    

    其实理解了上面的话,这一段的代码也比较容易理解,只是因为自己之前进了牛角尖,所以特意记录下来:

    1. 3s后 promiseA状态变成resolve
    2. 5s后 promiseB被resolved, 追随promiseA的状态
    3. 因为promiseA的状态为resolve, 所以打印 ccc

    V8中的async await和Promise

    在进入正题之前,我们可以先看下面这段代码:

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

    V8团队的博客中, 说到这段代码的运行结果有两种:

    Node8(错误的):

    tick a  
    tick b  
    after: await
    

    Node10(正确的):

    after await  
    tick a  
    tick b
    

    ok, 问题来了, 为啥是这个样子?
    先从V8对于await的处理说起, 这里引用一张官方博客的图来说明Node8 await的伪代码:

    Node8 await

    image

    对于上面的例子代码翻译过来就(该代码引用自V8是怎么实现更快的async await)是:

    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");
    });
    

    很明显,内部那一句 new Promise(res => res(p)); 代码就是一个状态跟随,即promise追随p的状态,那这跟上面的结果又有什么关系?

    在继续深入之前, 我们还需要了解一些概念:

    task和microtask

    JavaScript 中有 task 和 microtask 的概念。 Task 处理 I/O 和计时器等事件,一次执行一个。 Microtask 为 async/await 和 promise 实现延迟执行,并在每个任务结束时执行。 总是等到 microtasks 队列被清空,事件循环执行才会返回。

    如官方提供的一张图:
    image

    EnqueueJob、PromiseResolveThenableJob和PromiseReactionJob

    EnquequeJob: 存放两种类型的任务, 即PromiseResolveThenableJob和PromiseReactionJob, 并且都是属于microtask类型的任务

    PromiseReactionJob: 可以通俗的理解为promise中的回调函数

    PromiseResolveThenableJob(promiseToResolve, thenable, then): 创建和 promiseToResolve 关联的 resolve function 和 reject function。以 then 为调用函数,thenable 为this,resolve function和reject function 为参数调用返回。(下面利用代码讲解)

    状态跟随的内部

    再以之前的代码为例子

    const promiseA = new Promise((resolve) => {
      resolve('ccc')
    })
    const promiseB = new Promise(res => {
      res(promiseA)
    })
    

    当promiseB被resolved的时候, 也就是将一个promise(代指A)当成另外一个promise(代指B)的resolve参数,会向EnquequeJob插入一个PromiseResolveThenableJob任务,PromiseResolveThenableJob大概做了如下的事情:

    () => { 
      promiseA.then(
        resolvePromiseB, 
        rejectPromiseB
      );
    }
    

    并且当resolvePromiseB执行后, promiseB的状态才变成resolve,也就是B追随A的状态

    Node 8中的流程

    1. p处于resolve状态,promise调用then被resolved,同时向microtask插入任务PromiseResolveThenableJob  
    2. p.then被调用, 向microtask插入任务tickA  
    3. 执行PromiseResolveThenableJob, 向microtask插入任务resolvePromise(如上面的promiseA.then(...))  
    4. 执行tickA(即输出tick: a),返回一个promise, 向microtask插入任务tickB  
    5. 因为microtask的任务还没执行完, 继续  
    6. 执行resolvePromise, 此时promise终于变成resolve, 向microtask插入任务'after await'  
    7. 执行tickB(即输出tick: b)  
    8. 执行'after await'(即输出'after await')
    

    Node 10 await

    老规矩, 先补一张伪代码图:
    image
    翻译过来就是酱紫:

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

    因为p是一个promise, 然后Promise.resolve会直接将P返回,也就是

    p === promise // true
    

    因为直接返回了p,所以省去了中间两个microtask任务,并且输出的顺序也变得正常,也就是V8所说的更快的async await

    实现一个Promise

    先实现一个基础的函数

    function Promise(cb) {
      const that = this
      that.value = undefined // Promise的值
      that.status = 'pending' // Promise的状态
      that.resolveArray = [] // resolve函数集合
      that.rejectArray = []  // reject函数集合
    
      function resolve(value) {
        if (value instanceof Promise) {
          return value.then(resolve, reject)
        }
        setTimeout(function() {
          if (that.status === 'pending') { // 处于pending状态 循环调用
            that.value = value
            that.status = 'resolve'
            for(let i = 0; i < that.resolveArray.length; i++) {
              that.resolveArray[i](value)
            }
          }
        })
      }
      function reject(reason) {
        if (reason instanceof Promise) {
          return reason.then(resolve, reject)
        }
        setTimeout(function() {
          if (that.status === 'pending') { // 处于pending状态 循环调用
            that.value = reason
            that.status = 'reject'
            for(let i = 0; i < that.rejectArray.length; i++) {
              that.rejectArray[i](reason)
            }
          }
        })
      }
    
      try {
        cb(resolve, reject)
      } catch (e) {
        reject(e)
      }
    }
    Promise.prototype.then = function(onResolve, onReject) {
      var that = this
      var promise2 // 返回的Promise
    
      onResolve = typeof onResolve === 'function' ? onResolve : function(v) { return v }  //如果不是函数 则处理穿透值
      onReject = typeof onReject === 'function' ? onReject : function(v) { return v } //如果不是函数 则处理穿透值
    
      if (that.status === 'resolve') {
        return promise2 = new Promise(function(resolve, reject) {
          setTimeout(function() {
            try {
              const x = onResolve(that.value)
              if (x instanceof Promise) { // 如果onResolved的返回值是一个Promise对象,直接取它的结果做为promise2的结果
                x.then(resolve, reject)
              } else {
                resolve(x)
              }
            } catch (e) {
              reject(e)
            }
          })
        })
      }
    
      if (that.status === 'reject') {
        return promise2 = new Promise(function(resolve, reject) {
          setTimeout(function() {
            try {
              const x = onResolve(that.value)
              if (x instanceof Promise) { // 如果onResolved的返回值是一个Promise对象,直接取它的结果做为promise2的结果
                x.then(resolve, reject)
              } else {
                reject(x)
              }
            } catch (e) {
              reject(e)
            }
          })
        })
      }
    
      if (that.status === 'pending') {
        return promise2 = new Promise(function(resolve, reject) {
          that.resolveArray.push(function(value) {
            try {
              var x = onResolve(value)
              if (x instanceof Promise) {
                x.then(resolve, reject)
              }
            } catch (e) {
              reject(e)
            }
          })
          that.rejectArray.push(function(reason) {
            try {
              var x = onReject(reason)
              if (x instanceof Promise) {
                x.then(resolve, reject)
              }
            } catch (e) {
              reject(e)
            }
          })
        })
      }
    }
    Promise.prototype.catch = function(onReject) {
      return this.then(null, onReject)
    }
    

    参考

    v8是怎么实现更快的 await ?深入理解 await 的运行机制
    V8中更快的异步函数和promise
    剖析Promise内部结构,一步一步实现一个完整的、能通过所有Test case的Promise类
    PromiseA+
    ES6入门
    深入Promise

  • 相关阅读:
    Linux 安装jdk 报错 Error: could not find libjava.so Error: Could not find Java SE Runtime Environment.
    k8s 挂载ceph rbd
    spinnaker结合表达式实现发版时下拉列表选择docker image
    istio环境集成zipkin
    istio 日志打印 request body 和respon body
    jenkins任务中无法执行sudo,管理员操作
    ansible根据shell命令返回值,判断是否应该急继续往下执行
    ReplicaSetController、ReplicationController
    kubeproxy
    PodGCController
  • 原文地址:https://www.cnblogs.com/Darlietoothpaste/p/10256357.html
Copyright © 2020-2023  润新知