最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Promise 超详细源码分析,保证你能看懂

    正文概述 掘金(叶藏锋)   2021-03-08   524

    这个周编码过程中使用了NodeJs去处理图片,因为图片处理的接口全都是Promise,就导致了我的程序中充满了Promise的嵌套返回,then的结果中有返回了另一个Promise,另一个Promise中一系列中的then中有的又会返回新的Promise,这一度让我变得混乱。

    理清楚后,不禁对Promise为什么能够如此神奇产生了好奇,于是去阅读了它的源码,才发现有些习以为常的功能背后的设计的奇妙。

    Promise 使用

    ES6提供Promise构造函数,我们创造一个Promise实例,Promise构造函数接收一个函数作为参数,这个传入的函数有两个参数,分别是两个函数 resolvereject作用是,resolve将Promise的状态由未成功变为成功,将异步操作的结果作为参数传递过去;相似的是reject则将状态由未失败转变为失败,在异步操作失败时调用,将异步操作报出的错误作为参数传递过去。 实例创建完成后,可以使用then方法分别指定成功或失败的回调函数,比起f1(f2(f3))的层层嵌套的回调函数写法,链式调用的写法更为美观易读

    我们在创建一个新的Promise的时候需要传入一个函数作为参数,这个传入的函数有两个参数,分别是两个函数,resolve以及reject,函数体内部执行异步操作,然后根据操作的结果是否为需要的结果调用不同的函数,如果获得了正确的返回,就调用resolve函数,并将你需要返回的结果作为参数,如果没有获取正确的返回,就调用reject函数,并将错误信息作为参数,这样外部就可以获取错误信息了。举个简单的例子:

    const promise = new Promise((resolve, reject) => {
        setTimeout(() => {
            const num = Math.random();
            num > .5 ? resolve(`success:${num}`) : reject(`fail:${num}`);
        }, 1000);
    });
    
    promise.then((resolveVal) => {
        console.log(val);
    }).catch(rejectVal => {
        console.log(rejectVal)
    })
    

    这个例子创建了一个Promise,在一秒钟之后生成一个随机数,如果随机数大于0.5就返回调用resolve,如果小于0.5,如果小于0.5就调用reject。

    当执行了resolve时,Promise的状态会变为resolved,并会执行then中传入的函数,其实就相当于then中传入的函数会被当作resolve函数执行。同理,当执行了reject时,Promise的状态会变为Rejected,这个时候会利用catch中传入的函数作为reject函数去执行。

    总的来说,then和 catch时为Promise加载了两个resolve和reject时具体需要执行的函数。

    then的规则

    • then方法下一次的输入需要上一次的输出

    • 如果一个promise执行完后 返回的还是一个promise,会把这个promise 的执行结果,传递给下一次then中。也就是说如果你在PromiseA的then中返回了PromiseB,那么PromiseB的结果会作为PromiseA下一步then的入参。

      const promiseA = new Promise((resolve, reject) => {
          ...
      });
      const promiseB = new Promise((resolve, reject) => {
            
      })
      promiseA.then((resolveA) => {
          return promiseB;
      }).then((resolveB) => {
            
      })
      

      这种情况下第二个then的参数就是promise的返回结果。

    • 如果then中返回的不是Promise对象而是一个普通值,则会将这个结果作为下次then的成功的结果

    • 如果当前then中失败了 会走下一个then的失败

    • 如果返回的是undefined 不管当前是成功还是失败 都会走下一次的成功

    • then中不写方法则值会穿透,传入下一个then

    • then函数中的return val 与Promise resolve(val)相同

    举例说明:

    //example1
    promise1 = new Promise((resolve) => {
        setTimeout(() => {
            resolve('promise1');
        }, 1000)
    })
    

    当参数是一个非promise的时候,1秒后promise的状态立即变成resolve,并执行then里面的事件.

    //example2
    promise1 = new Promise((resolve) => {
        setTimeout(() => {
            promise2 = new Promise((resolve, reject) => {
                resolve('promise2');
            })
            resolve(promise2);
        }, 1000)
    })
    

    当参数是另一个promise的时候,这时promise1的状态由promise2来决定,什么时候promise2变化了状态,promise1的状态也会相应的变化,并且状态保持一致.

    //example3
    promise1 = new Promise((resolve) => {
        resolve('promise1');
    })
    promise2 = promise1.then((data) => {
        return 'promise2';
    })
    

    当回调函数里面直接return一个非promise,和上面的example1一样,当前的promise2状态变为resolve。相当于执行了(resolve('非promise'))

    //example4
    promise1 = new Promise((resolve) => {
        resolve('promise1');
    })
    promise2 = promise1.then((data) => {
        promise3 = new Promise((resolve, reject) => {
            resolve('promise3');
        })
        return promise3;
    })
    

    当回调函数里面直接return一个promise3,和上面example2一样,当前promise2的状态依赖于primise3,相当于执行了(resolve(promise3))

    //example5
    promise1 = new Promise((resolve) => {
        resolve('promise1');
    })
    promise2 = promise1.then((data) => {
        console.log( iamnotundefined );
    })
    

    当回调函数里面代码报错了,并且没有被catch到的,当前promise状态变为reject.(异步的error代码catch不到,不会影响promise状 态变化)

    catch的规则

    • 执行reject的时候会调用catch
    • 前面出现任何没有被处理的错误时会执行catch

    源码分析

    Promise源码地址

    下面的注释说明了对Promise状态的可能值:

    0 - 等待中 1 - 满足条件 (值为 _value) 2 - 拒绝条件 (值为 _value) 3 - 采用了另一个Promise的状态和值

    在正式声明Promise之前,为了减少对try catch在代码中显示,定义了几个工具函数,

    工具函数

    function noop() {} //空回调函数,用于then
    
    // States:
    //
    // 0 - pending
    // 1 - fulfilled with _value
    // 2 - rejected with _value
    // 3 - adopted the state of another promise, _value
    //
    // once the state is no longer pending (0) it is immutable
    
    // All `_` prefixed properties will be reduced to `_{random number}`
    // at build time to obfuscate them and discourage their use.
    // We don't use symbols or Object.defineProperty to fully hide them
    // because the performance isn't good enough.
    
    
    // to avoid using try/catch inside critical functions, we
    // extract them to here.
    var LAST_ERROR = null;
    var IS_ERROR = {};
    function getThen(obj) {
      try {
        return obj.then;
      } catch (ex) {
        LAST_ERROR = ex;
        return IS_ERROR;
      }
    }
    
    function tryCallOne(fn, a) {
      try {
        return fn(a);
      } catch (ex) {
        LAST_ERROR = ex;
        return IS_ERROR;
      }
    }
    function tryCallTwo(fn, a, b) {
      try {
        fn(a, b);
      } catch (ex) {
        LAST_ERROR = ex;
        return IS_ERROR;
      }
    }
    

    声明

    module.exports = Promise;
    function Promise(fn) {
      if (typeof this !== 'object') {
        throw new TypeError('Promises must be constructed via new');
      }
      if (typeof fn !== 'function') {
        throw new TypeError('Promise constructor\'s argument is not a function');
      }
      this._deferredState = 0;//这个状态用于表明未来的状态会是怎么样的,只有当前的Promise状态依赖另一个Promise时才有用,也就是resolve了一个Promise
      this._state = 0;//当前Promise的状态
      this._value = null;//当前Promise的resolve的值
      this._deferreds = null;//当_deferredState变为成功,也就是大于2时,执行的回调函数数组
      if (fn === noop) return;
      doResolve(fn, this);
    }
    

    函数Promise接受一个函数作为其参数,必须通过new来创建。

    初始化 _deferredState 和 _state为 0, _value和_deferreds为null, 如果传入函数是一个空函数,那么直接返回。 正常情况下,进入 doResolve 开始流程。

    doResolve

    function doResolve(fn, promise) {
      var done = false;
        //注意,fn就是我们new Promise时传入的(resolve, reject)=> { if success resolve else reject},tryCallTwo会将第二个参数传给resolve,将第三个参数传给reject,这样当我们在声明的Promise中调用resolve时实际上调用的时trayCallTwo的第二个参数。
      var res = tryCallTwo(fn, function (value) {
        if (done) return;// 防止运行两次
        done = true;
        resolve(promise, value);
      }, function (reason) {
        if (done) return;
        done = true;
        reject(promise, reason);
      });
      if (!done && res === IS_ERROR) {
        done = true;
        reject(promise, LAST_ERROR);
      }
    }
    

    这里同步的直接调用传入的函数,讲两个函数 (即外部编写的resolve和reject)作为参数传入 , 调用完成后检查下是否是没完成的情况下出错了,如果是直接reject. 针对传入的resolve函数和reject函数,等待结果后,如果尚未完成,则通过本文件中定义的resolve和reject来继续流程.

    注意tryCallTwo函数的第二个和第三个参数都是一个函数,这两个函数的参数其实就是我们new一个新的Promise的时候传入的两个函数而这段代码中的resolve和reject则是Promise内部定义的方法

    resolve & reject

    function resolve(self, newValue) {
     // 一个Promise的解决结果不能是自己 (因为根据一开始我们提到的then的原则中,如果你返回了一个新的Promise,那么当前Promise的状态就会依赖于新的Promise,如果自己依赖自己,那么就会一直循环依赖并处于pending状态)
        
    // 这里的newValue其实就是我们定义的Promise实例中resolve的参数,依照前面的使用方法,它可以是字符串,数组等,也可以是另一个一个Promise
      if (newValue === self) {
        return reject(
          self,
          new TypeError('A promise cannot be resolved with itself.')
        );
      }
      // 当新的值也就是resolve函数的参数存在并且类型是对象或者函数的时候
      // typeof Promised实例 === 'object'
      // 也就是说这个if成功的条件是resolve了一个对象,函数或者Promise实例
      if (
        newValue &&
        (typeof newValue === 'object' || typeof newValue === 'function')
      ) {
        var then = getThen(newValue);  // let then = newValue.then
        if (then === IS_ERROR) { //IS_ERROR就是上面所说的工具中声明的,就是一个空对象,只有当return newValue.then发生异常时才会为IS_ERROR
          return reject(self, LAST_ERROR);
        }
        if (
          then === self.then &&
          newValue instanceof Promise // 如果resvole的是一个Promise,并且这个Promise的then与当前Promise的then相同时(这个then一般是相同的,都是定义在Promise的原型上的),直接就用当前Promise的结果为最终结果。
        ) {
          self._state = 3; //状态3说明采用另一个Promise作为结果
          self._value = newValue;
          finale(self); // 那么采用这个Promise的结果
          return;
        } else if (typeof then === 'function') {//走到这里就说明,resolve了一个有then方法的对象(或者一个Promise,并且和当前Promise的then不同,这种情况比较少见)
          doResolve(then.bind(newValue), self); // 递归调用doResolve,刚才我们也看了,doResolve的第一个参数是我们new Promise时传入的函数,第二个参数是当前的Promise实例的指针,也就是在递归中执行到这个then函数时,其内部的this指向的是这里的newValue,这里self没变
          return;
        }
      }
      //上面的if都没有return,才会走到这里,到了这里就说明,resolve了普通的值,比如数字,字符串,标记完成,进入结束流程
      self._state = 1; //状态1说明进入成功状态
      self._value = newValue;
      finale(self);
    }
    
    function reject(self, newValue) {
      //设置reject状态和理由
      self._state = 2;
      self._value = newValue;
      if (Promise._onReject) {
        Promise._onReject(self, newValue); //过程回调通知
      }
      finale(self); //结束 
    }
    
    //这个函数只有self的状态不为0的时候才会执行,这个时候执行handle就会执行传入的第二个参数,也就是_deferreds,_deferredState为1表明_deferreds是一个Handler,_deferredState为2说明_deferreds是Handler的数组,全都执行完了,再把_deferreds赋值为空
    //也就是说finale是把所有的Handler执行一次
    // 每个Hander都是通过then方法传入的,then方法有两个参数,分别是onFulfilled,onRejected,也就是resolve时应该执行的函数和reject时应该执行的函数
    // 如果是_state为3的时候执行finale,其实就是先把当前Promise通过then创建的Handler挂到了它所依赖地Promise实例的_deferreds上去
    function finale(self) {
      if (self._deferredState === 1) {
        handle(self, self._deferreds);
        self._deferreds = null;
      }
      if (self._deferredState === 2) {
        for (var i = 0; i < self._deferreds.length; i++) {
          handle(self, self._deferreds[i]);
        }
        self._deferreds = null;
      }
    }
    

    然就冒出来之前未曾触及到的_deferredState和_deferreds的使用,他们是什么用的? 在回答这些之前,我们先回想下在Promise的时候中,当创建并返回了promise之后,下面的操作就是then来获取结果了 (当然也包括catch), 那么对于这个then的实现我们先来看一下:

    then

    //then的参数是两个函数,onFulfilled是我们resolve时调用的回调函数,onRejected是我们reject时调用的回调函数
    Promise.prototype.then = function(onFulfilled, onRejected) {
      if (this.constructor !== Promise) {
        return safeThen(this, onFulfilled, onRejected);
      }
      var res = new Promise(noop);
      handle(this, new Handler(onFulfilled, onRejected, res));
      return res;
    };
    

    其中的safeThen和then的用法基本一致,都是创建了一个异步的空回调res,然后使用onFulfilled, onRejected和res来创建 Handler。那么核心就在handle这个函数上了:

    function handle(self, deferred) {
      //self._state === 3说明self指向的Promise实例的状态依赖另一个Promise实例,这个实例就是self._value
      //也就是说通过这个循环,最终self最终会指向一个状态只依赖自己的Promise实例
      //也就是说假设self是p1,它依赖p2的状态,当p1调用handle的时候,并且p2还没有resolve,其实是把p1通过then创建的Handler挂到了p2的_deferreds上去,当p2 resolve的时候依次执行。
      while (self._state === 3) {
        self = self._value;
      }
      if (Promise._onHandle) { // for injection - not in main loop
        Promise._onHandle(self);
      }
      //如果这个状态只依赖自己的Promise实例还没有结果,就把传入的回调函数先保存起来
      // _deferredState 为0表明还没有保存过回调函数,那么就把回调函数赋值给_deferreds,这个时候_deferreds只是一个回调函数
      // _deferredState 为1表明保存过回调函数,那么就把回调函数和原有的保存在_deferreds的回调函数构造成一个数组重新赋值给_deferreds,这个时候_deferreds只是一个回调函数的数组
      // _deferredState 为2表明_deferreds已经是一个回掉函数的数组了,就push就可以了
      // 直到某一次调用handle,self的状态不为0了,才会执行handleResolved
      if (self._state === 0) {
        if (self._deferredState === 0) {
          self._deferredState = 1;
          self._deferreds = deferred;
          return;
        }
        if (self._deferredState === 1) {
          self._deferredState = 2;
          self._deferreds = [self._deferreds, deferred];
          return;
        }
        self._deferreds.push(deferred);
        return;
      }
      handleResolved(self, deferred);
    }
    

    这个函数的参数就是deferred, 那么说明deferred就是Handler, 结合意思,指代的就是延迟的处理。明白点说,就是完成promise之后所需要做的事情。 那么具体的过程是怎么样的呢? 首先判断当前状态是不是依赖于另一个promise, 是的话则通过while等待 然后onHandle只是个提供给外部的进度回调,这里先无视 当状态为0的时候,这里就是设置未来的处理过程了,

    如果未来状态没有设置过(0), 那么设置回调(deferred) 为单独回调
    如果未来状态设置过了 (1),  那么设置回调 (deferred) 进入回调数组
    如果其他状态 (2 +),那么直接进入回调数组。
    对状态0情况下的处理这里就返回了。 因为在这个时候,是promise同步执行过来的then, 设置好未来处理的函数过程。 
    

    当状态非0的时候, 就进入了handleResolved,这应该就是完成后处理结束的地方了。 等等,上面只是从then出发进入handle的,那时候应该promise还没有完成,处理完成的调用一定是在别的地方。 通过搜索handle的调用可以看到还有在finale函数中, 这样就和上面连接上了,我们先回顾下什么时候会调用finale呢?

    1 状态3 等待其他promise的结果时候 - 这里会进入等待
    2 状态1 完成的时候
    3 状态2 reject的时候
    

    可以看到只有在promise结束或者依赖其他promise的时候,才会进入finale.

    function finale(self) {
      if (self._deferredState === 1) {
        handle(self, self._deferreds);
        self._deferreds = null;
      }
      if (self._deferredState === 2) {
        for (var i = 0; i < self._deferreds.length; i++) {
          handle(self, self._deferreds[i]);
        }
        self._deferreds = null;
      }
    }
    

    finale中会将之前放入的deffereds 一一取出 调用handle, 这时state均为非0,直接进入handleResolved, 代码如下:

    function handleResolved(self, deferred) {
      asap(function() {
        // self._state 为 1,就执行then函数的第一个参数,就是成功的回调函数,否则执行reject的回调函数
        var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
        if (cb === null) {
          if (self._state === 1) {
            resolve(deferred.promise, self._value);
          } else {
            reject(deferred.promise, self._value);
          }
          return;
        }
        var ret = tryCallOne(cb, self._value);
        if (ret === IS_ERROR) {
          reject(deferred.promise, LAST_ERROR);
        } else {
          resolve(deferred.promise, ret);
        }
      });
    }
    

    这里就比较简单的,通过异步的asap调用,如果没有onFulfilled(onRejected失败情况),则直接调用resolve(reject), 如果有则先调用onFulfilled(onRejected失败情况),根据结果来调用resolve(reject)。

    等等。。这里的resolve和reject不是在上面的流程中有出现了么?请注意这里resolve和 rejected的promise, 这个promise是在then的时候创建的空promise,也就是意味这什么都不会执行 (直接进入finale 无handle情况)。 所以真正影响这里流程的是 对于deferred.onFulfilled 或者 deferred.onRejected的回调执行,执行完回调 这个promise的执行过程就完成了。

    综上, promise的执行过程是这样的

    • 创建Promise
    • 设置需要执行的函数,也就是new Promise是传入的函数
    • 设置完成的回调,也就是then传入的两个函数,第一个是resolve时执行的,一个是reject执行的,如果resolve了一个Promise2,那么当前then创建的Handler会挂载到Promise2上,等到Promise2 resolve了再一起执行。
    • 开始执行函数
    • 根据执行结果选择回调

    另外提一句safeThen, safeThen的作用是当调用then的时候环境this已经不是Promise的情况下能够继续安全执行then。

    参考文章:

    juejin.im/post/5caf14…

    juejin.im/entry/59996…

    www.jianshu.com/p/b63ec30ee…


    起源地下载网 » Promise 超详细源码分析,保证你能看懂

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元