最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Promise源码指南

    正文概述 掘金(Duncan)   2021-04-01   829

    Promise源码指南

    前言

    什么是Promise?

    相信许多从事前端的小伙伴们都用过Promise,为了解决异步编程的弊端(地狱回调等问题),ES6提供了一个强大的东西,那就是Promise。 Promise是将异步任务转换为同步任务的一个构造函数,通过resolve,reject改变任务的状态,必不可少的then方法用来收Promise的值,这些都是Promise的基本使用。那么Promise是如何处理状态的,又是如何实现resove,reject方法的,又是如何实现链式调用的呢,如果你不知道,那么这篇文章可以帮到你,下面我们一起来解析一下Promise到底是如何实现的,相信看完这篇文章,每个人都可以写出属于自己的Promise方法。

    这里引入github上的一份符合Promise A+规范的源码 https://github.com/then/promise
    

    函数对象Promise

    我们先来看看src/index.js这个文件

    一些必要的定义

    // 定义空函数
    function noop() {}
    
    // 用来存储错误信息
    var IS_ERROR = {}
    
    // 获取实例的then
    function getThen(obj) {
      try {
        return obj.then;
      } catch (ex) {
        LAST_ERROR = ex;
        return IS_ERROR;
      }
    }
    
    // 执行then方法回调函数
    function tryCallOne(fn, a) {
      try {
        return fn(a);
      } catch (ex) {
        LAST_ERROR = ex;
        return IS_ERROR;
      }
    }
    
    // 执行Promise构造函数
    function tryCallTwo(fn, a, b) {
      try {
        fn(a, b);
      } catch (ex) {
        LAST_ERROR = ex;
        return IS_ERROR;
      }
    }
    

    Promise构造函数

    function Promise(fn) {
      // 校验实例
      if (typeof this !== 'object') {
        throw new TypeError('Promises must be constructed via new');
      }
      // 校验Promise的构造函数
      if (typeof fn !== 'function') {
        throw new TypeError('Promise constructor\'s argument is not a function');
      }
      // 存储的实例状态 0代表还未存储 1代表存储了1个 2代表存储了2个
      this._deferredState = 0;
      // Promise的状态  0代表padding  1代表Fulfilled  2代表Rejected 3代表resolve传入的是promise实例
      this._state = 0;
      // Fulfilled的值
      this._value = null;
      // 用来存储调用then后的实例
      this._deferreds = null;
      if (fn === noop) return;
      // 处理Promise的参数
      doResolve(fn, this);
    }
    //以下先不做解释
    newPromise._onHandle = null;
    newPromise._onReject = null;
    newPromise._noop = noop;
    

    当使用new操作符实例化Promise的时候,必须传入Promise的构造函数fn,否则将抛出错误
    然后初始化实例的状态和值 最后调用doResolve方法
    下面我们一起来看一下doResolve这个方法

    doResolve方法

    function doResolve(fn, promise) {
      // done防止重复触发
      var done = false;
      // tryCallTwo  用于处理并挂载reolve,reject方法
      // 传入3个参数,Promise构造函数本身,resolve回调,reject回调
      var res = tryCallTwo(fn, function (value) {
        if (done) return;
        done = true;
        // 处理resolve方法
        resolve(promise, value);
      }, function (reason) {
        if (done) return;
        done = true;
        // 处理reject方法
        reject(promise, reason);
      });
      // 报错则直接reject
      if (!done && res === IS_ERROR) {
        done = true;
        reject(promise, LAST_ERROR);
      }
    }
    

    doResolve方法接收两个参数(Promise的构造函数,Promise的实例也就是this)
    这里的tryCallTwo扮演了一个重要角色,它执行了构造函数fn,传入三个参数(fn构造函数,resolve回调函数,reject回调函数)
    我们回过头来看看tryCallTwo这个函数

    // 执行Promise构造函数
    function tryCallTwo(fn, a, b) {
        //...
        fn(a, b);
        //...
    }
    

    这里的a,b就是Promise的resolvereject
    执行fn并传入ab
    然后resolve和reject又分别执行了resolve方法和reject方法
    当实例调用了resolve之后就会执行resolve方法,下面来看看resove这个方法

    resolve方法

    function resolve(self, newValue) {
      // 防止resolve的值传入实例本身
      if (newValue === self) {
        return reject(
          self,
          new TypeError('A promise cannot be resolved with itself.')
        );
      }
      // 这里的if主要用于处理resolve一个Promise
      if (
        newValue &&
        (typeof newValue === 'object' || typeof newValue === 'function')
      ) {
        // 获取Promise的then方法
        var then = getThen(newValue);
        if (then === IS_ERROR) {
          return reject(self, LAST_ERROR);
        }
        // 如果传入的是Promise的实例
        if (
          then === self.then &&
          newValue instanceof newPromise
        ) {
          self._state = 3;
          // 直接把传入的Promise实例挂载到value
          self._value = newValue;
          finale(self);
          return;
        } else if (typeof then === 'function') {  //如果传入带有then方法
          // 把then当作构造函数并且把this指向这个then的对象
          doResolve(then.bind(newValue), self);
          return;
        }
      }
      self._state = 1;
      self._value = newValue;
      finale(self);
    }
    

    resolve方法一开始会先判断resolve传入的值,如果resolve传入的是一个Promise实例,将会进行以下处理
    将实例的状态变为3
    将实例的值变为传入的Promise实例
    调用finale方法
    如果resolve传入的是Promise实例并且包含then方法则调用doResolve执行这个实例的构造函数

    如果resolve传入的是普通值而不是Promise实例,则做以下处理
    将实例的状态变为1
    将实例的值变为resolve传入的值
    调用finale方法

    reject方法

    function reject(self, newValue) {
      // reject的时候状态变为2
      self._state = 2;
      // 保存错误信息到_value
      self._value = newValue;
      if (newPromise._onReject) {
        newPromise._onReject(self, newValue);
      }
      finale(self);
    }
    

    当Promise执行reject的时候此时状态变为2
    保存reject的错误信息到_value 调用finale方法

    finale方法

    function finale(self) {
      // 只调用一次then
      if (self._deferredState === 1) {
        handle(self, self._deferreds);
        self._deferreds = null;
      }
      // 调用多次then
      if (self._deferredState === 2) {
        // console.log(self._deferreds);
        for (var i = 0; i < self._deferreds.length; i++) {
          handle(self, self._deferreds[i]);
        }
        self._deferreds = null;
      }
    }
    

    finaale方法在这里的作用就相当于handle方法的中转站,根据不同的情况去调用handle方法
    上面有提到过_deferredState是用来记录存储的实例状态
    同一个Promise对象下_deferredState的值是随着then调用的次数决定的,为什么说只有在同一个Promise对象下才会触发呢,我们来看下面一个小例子

    const promise2 = new Promise((resolve,reject) => {
        setTimeout(() => {
          resolve('哈哈哈')
        }, 500);
      })
      .then(res => {})	//第一次then
      .then(res => {})	//第二次then
    

    到这里很多小伙伴会以为代码会在’if(self._deferredState === 2){}‘这个判断里面执行
    事实上这样去调用then的情况下,_deferredState的值永远只会=1,只会在’if(self._deferredState === 1){}‘的这个判断里面去执行
    到这里小伙伴们又会说,不对啊,我这个不是在同一个Promise对象下吗,我不是只实例化了一次吗?
    这里在使用Promise的时候确实是只实例化了一次,但是每次调用then方法返回的Promise跟实例的Promise并不是同一个引用,也就是说,这里的self并不是实例出来的对象,后面会详细介绍then是怎么返回Promise对象的

    promise2.then(res => {
      console.log(res);
    })
    promise2.then(res => {
      console.log(res);
    })
    

    只有这样调用then,才会执行’if(self._deferredState === 2){}‘这个判断

    Promise.prototype.then

    Promise.prototype.then = function(onFulfilled, onRejected) {
      if (this.constructor !== Promise) {
        return safeThen(this, onFulfilled, onRejected);
      }
      // 新建一个promise实例
      var res = new Promise(noop);
      // // new Handler 构建了一个包含新的promise实例和resolve,reject方法的对象
      handle(this, new Handler(onFulfilled, onRejected, res));
      // 每次then处理完之后返回一个新的promise实例
      return res;
    };
    

    接收两个参数resolvereject函数
    一开始先判断实例的构造函数是不是Promise(防止外部修改prototype.constructor)
    这里的safeThen函数不作过多解释,主要用于实例化外部实例的构造函数并返回实例
    创建一个空的Promise实例给res
    我们看看下面这句代码主要做了什么

    handle(this, new Handler(onFulfilled, onRejected, res));
    

    我们拆开来看,先看看new Handler实例化得到了什么

    // 这个函数的作用就是每次then的时候通过这个构造函数根据resolve和reject构建一个新的对象
    function Handler(onFulfilled, onRejected, promise){
      this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
      this.onRejected = typeof onRejected === 'function' ? onRejected : null;
      this.promise = promise;
    }
    

    由此可见通过实例化Handler函数可以得到一个大概长这样子的对象

    Promise源码指南

    所以handle函数传入了2个参数,一个是this,一个是像这样子的对象,那么,接下来就可以看handle函数做了什么事情了

    function handle(self, deferred) {
      // resolve传入的是promise的实例,this(上下文)则改成传入的promise实例
      while (self._state === 3) {
        self = self._value;
      }
      if (newPromise._onHandle) {
        newPromise._onHandle(self);
      }
      // 当padding状态时(没有调用resolve也没有调用reject)
      // 存储新的promise实例
      if (self._state === 0) {
        if (self._deferredState === 0) {
          self._deferredState = 1;
          self._deferreds = deferred;
          return;
        }
        // 已经调用过1次then
        if (self._deferredState === 1) {
          self._deferredState = 2;
          self._deferreds = [self._deferreds, deferred];
          return;
        }
        // 多次then
        self._deferreds.push(deferred);
        return;
      }
      handleResolved(self, deferred);
    }
    

    看上面的代码注释大家都应该知道了,handle函数主要就是用作修改_deferredState的值和保存每次then生成的新实例

    最后return res 每次调用then方法都返回一个新的Promise实例

    链式调用

    细心的你可能已经发现了在前面的handle方法里的最下面调用了一个名为handleResolved的函数,话不多说直接上代码

    function handleResolved(self, deferred) {
      // asap(function() {
      //   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);
      //   }
      // });
    
      // resolve后,获取then的回调
      var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
      // 如果then没有回调,则手动调用回调
      if (cb === null) {
        if (self._state === 1) {
          resolve(deferred.promise, self._value);
        } else {
          reject(deferred.promise, self._value);
        }
        return;
      }
      // 获取then的返回值
      var ret = tryCallOne(cb, self._value);
      if (ret === IS_ERROR) {
        reject(deferred.promise, LAST_ERROR);
      } else {
        resolve(deferred.promise, ret);
      }
    }
    

    其实源码还引入了asap这个库,我先把这个asap函数注释掉了,理由是懒得用npm,本文全程是html+js+live-server
    但是大家千万不要忽略asap这个函数!!!
    纵观全文到现在,大家好像并没有发现源码有一点点异步的信息,大家都知道Promise是异步执行的,就是靠asap函数,通过setImmediate这个核心方法去异步执行asap里面的东西,有兴趣的可以去翻翻asap的源码看下具体是怎么实现的
    这里只是为了更好的解析源码,没有asap那么Promise就没有意义了,ok我们回归正文

    这里就很好理解了
    通过tryCallOne函数得到then的返回值
    然后再次调用resolve,如果报错或者手动调用reject则调用reject,这样就完成了Promise的链式调用

    扩展

    src/es6-extensions.js

    定义

    var TRUE = valuePromise(true);
    var FALSE = valuePromise(false);
    var NULL = valuePromise(null);
    var UNDEFINED = valuePromise(undefined);
    var ZERO = valuePromise(0);
    var EMPTYSTRING = valuePromise('');
    
    function valuePromise(value) {
      var p = new newPromise(newPromise._noop);
      p._state = 1;
      p._value = value;
      return p;
    }
    

    Promise.resolve

    Promise.resolve = function (value) {
      // 判断value是否是Promise得实例
      if (value instanceof Promise) return value;
    
      // 因为0,'',null等会被隐式转换成false,所以这里做了精确判断
      if (value === null) return NULL;
      if (value === undefined) return UNDEFINED;
      if (value === true) return TRUE;
      if (value === false) return FALSE;
      if (value === 0) return ZERO;
      if (value === '') return EMPTYSTRING;
    
    
      // 这里的判断跟之前resolve方法一样都是判断传入的是否是一个Promise
      if (typeof value === 'object' || typeof value === 'function') {
        try {
          var then = value.then;
          if (typeof then === 'function') {
            return new Promise(then.bind(value));
          }
        } catch (ex) {
          return new Promise(function (resolve, reject) {
            reject(ex);
          });
        }
      }
      // 根据valuePromise方法返回一个新的Promise
      return valuePromise(value);
    };
    

    try/catch 用于捕获Promise.resolve传入的val是否包含then方法

    Promise.all

    Promise.all = function (arr) {
      var args = iterableToArray(arr);
    
      return new Promise(function (resolve, reject) {
        if (args.length === 0) return resolve([]);
        var remaining = args.length;
        function res(i, val) {
          if (val && (typeof val === 'object' || typeof val === 'function')) {
            // 如果val是Promise的实例
            if (val instanceof Promise && val.then === Promise.prototype.then) {
              // _state等于3  证明val实例的值也是一个Promise实例,把val替换成新的Promise实例
              while (val._state === 3) {
                val = val._value;
              }
              // resolved成功调用,递归处理resolved的值
              if (val._state === 1) return res(i, val._value);
              if (val._state === 2) reject(val._value);
              // 处于padding状态时调用then方法并手动处理值
              val.then(function (val) {
                res(i, val);
              }, reject);
              return;
            } else {
              // 如果不是promise的实例且包含then方法
              var then = val.then;
              if (typeof then === 'function') {
                var p = new Promise(then.bind(val));
                p.then(function (val) {
                  res(i, val);
                }, reject);
                return;
              }
            }
          }
          args[i] = val;
          // promise.all里面全部为fulFilled状态后
          if (--remaining === 0) {
            resolve(args);
          }
        }
        for (var i = 0; i < args.length; i++) {
          res(i, args[i]);
        }
      });
    };
    

    第一行用到了iterableToArray函数,这个函数的主要作用是把类数组转换成可被遍历的数组

    const p1 = new Promise(() => {})
    const p2 = new Promise(() => {})
    console.log(Array.isArray(Promise.all[p1,p2]))  //false
    
    // 兼容Array.form这个es6语法
    var iterableToArray = function (iterable) {
      if (typeof Array.from === 'function') {
        // ES2015+, iterables exist
        iterableToArray = Array.from;
        return Array.from(iterable);
      }
    
      // ES5, only arrays and array-likes exist
      iterableToArray = function (x) { return Array.prototype.slice.call(x); };
      return Array.prototype.slice.call(iterable);
    }
    

    使用Array.prototype.slice.call(x)对Array.from做了兼容

    Promise.all = function (arr) {
      var args = iterableToArray(arr);
    
      return new Promise(function (resolve, reject) {
        if (args.length === 0) return resolve([]);
        var remaining = args.length;
        function res(i, val) {
          ...
        }
        for (var i = 0; i < args.length; i++) {
          res(i, args[i]);
        }
      });
    };
    

    先不要看res方法,先看看怎么处理传进来的参数
    循环调用res处理传进来的每一项,第一个参数为下标,第二个参数是数组的每一项
    再来看下res方法

    function res(i, val) {
          if (val && (typeof val === 'object' || typeof val === 'function')) {
            // 如果val是Promise的实例
            if (val instanceof Promise && val.then === Promise.prototype.then) {
              // _state等于3  证明val实例的值也是一个Promise实例,把val替换成新的Promise实例
              while (val._state === 3) {
                val = val._value;
              }
              // resolved成功调用,递归处理resolved的值
              if (val._state === 1) return res(i, val._value);
              if (val._state === 2) reject(val._value);
              // 处于padding状态时调用then方法并手动处理值
              val.then(function (val) {
                res(i, val);
              }, reject);
              return;
            } else {
              // 如果不是promise的实例且包含then方法
              var then = val.then;
              if (typeof then === 'function') {
                var p = new Promise(then.bind(val));
                p.then(function (val) {
                  res(i, val);
                }, reject);
                return;
              }
            }
          }
          args[i] = val;
          // promise.all里面全部为fulFilled状态后
          if (--remaining === 0) {
            resolve(args);
          }
        }
    

    如果传进来的val(args的每一项)不是对象或者function的话,那么直接视为结果值把args[i]给替换掉

    args[i] = val;
    

    如果传进来的是一个Promise,则

    if (val instanceof Promise && val.then === Promise.prototype.then) {
          ...
    }
    

    如果传进来的val不是Promise且包含then方法,则

    else {
      // 如果不是promise的实例且包含then方法
      var then = val.then;
      if (typeof then === 'function') {
        var p = new newPromise(then.bind(val));
        p.then(function (val) {
          res(i, val);
        }, reject);
        return;
      }
    }
    

    重点在这一段

    // _state等于3  证明val实例的值也是一个Promise实例,把val替换成新的Promise实例
      while (val._state === 3) {
        val = val._value;
      }
      // resolved成功调用,递归处理resolved的值
      if (val._state === 1) return res(i, val._value);
      if (val._state === 2) reject(val._value);
      // 处于padding状态时调用then方法并手动处理值
      val.then(function (val) {
        res(i, val);
      }, reject);
      return;
    

    只有当Promise的状态为Fulfilled的时候,实例的value才会被正确的处理,否则会执行return,所以只要有一个Promise未能成功Fulfilled都不会执行resolve(args)

    //满足不了条件
    if (--remaining === 0) {
        resolve(args);
    }
    

    当得到所有结果值的时候(args[i] = val) == args[i] = val.value,调用resolve方法并传入结果数组args

    看个例子

    const promise2 = new Promise((resolve,reject) => {
        setTimeout(() => {
          resolve('哈哈哈')
        }, 700);
    })
    const promise3 = new Promise((resolve,reject) => {
        setTimeout(() => {
          resolve('哈哈哈2')
        }, 600);
    })
      
    newPromise.all([promise2,promise3])
    .then(res => {
        console.log(res);  //['哈哈哈','哈哈哈2']
    })
    

    这里无论什么时候resolve,最后得出的结果数组res都是按照Promise.all([])数组里面的顺序来输出的,这也印证了源码中为什么要把下标传入到res()的原因(res(i, args[i]))

    Promise.race

    Promise.race = function (values) {
      return new Promise(function (resolve, reject) {
        iterableToArray(values).forEach(function(value){
          Promise.resolve(value).then(resolve, reject);
        });
      });
    };
    

    Promise.race传入一个数组,用Promise.resolve处理每一项并调用then方法
    最后返回一个Promise实例
    这里的 Promise.resolve(value).then(resolve, reject)当传入的Promise谁的状态首先变为Fulfilled,谁就先调用then
    因为Promise执行resolve一次之后状态就不会改变,所以race传入多个Promise,谁的状态先变为Fulfilled,race就返回哪个

    const promise2 = new Promise((resolve,reject) => {
        setTimeout(() => {
          resolve('哈哈哈')
        }, 700);
    })
    const promise3 = new Promise((resolve,reject) => {
        setTimeout(() => {
          resolve('哈哈哈2')
        }, 600);
    })
    
    Promise.race([promise2,promise3])
    .then(res => {
        console.log(res);  //哈哈哈2
    })
    

    起源地下载网 » Promise源码指南

    常见问题FAQ

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

    发表评论

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

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

    联系作者

    请选择支付方式

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