Promise-Polyfill源码解析(3)

366 阅读4分钟

本篇将分析最后的catch、all、race方法。 首先是catch方法,回想下catch方法的使用方式,我们一般将其放在Promise链的最后,用来捕获拒绝的原因。因此,catch方法也应该定义在Promise的原型链上,我们来看其实现:

Promise.prototype['catch'] = function(onRejected) {
  return this.then(null, onRejected);
};

可以看到,catch方法就是一个低配版的then方法,只接收一个拒绝回调参数。因此,我们得出,catch方法并不是Promise链的终端,其后可以继续链式调用。

再来看race方法,race方法接收一个数组,当数组中任何一个Promise解决或拒绝,就返回一个解决或拒绝状态的Promise。其实现:

Promise.race = function(values) {
  return new Promise(function(resolve, reject) {
    for (var i = 0, len = values.length; i < len; i++) {
      values[i].then(resolve, reject);
    }
  });
};

整个方法返回了实例化的Promise对象,在回调参数中遍历传入的数组,调用每个数组元素的then方法,并传入resolve、reject。总的来看,其实现相当简洁,将Promise的状态交给每个数组元素的then方法来决定,因为状态一旦决定就不会再改变,因此也不需要一个标记来中途退出循环。

最后来看下all方法,all方法也接收一个数组,当数组中所有Promise都完成,返回一个完成状态的Promise对象,当数组中任一Promise被拒绝,返回一个拒绝状态的Promise对象。其实现:

Promise.all = function(arr) {
  return new Promise(function(resolve, reject) {
    if (!arr || typeof arr.length === 'undefined')
      throw new TypeError('Promise.all accepts an array');
    var args = Array.prototype.slice.call(arr);
    if (args.length === 0) return resolve([]);
    var remaining = args.length;

    function res(i, val) {
      try {
        if (val && (typeof val === 'object' || typeof val === 'function')) {
          var then = val.then;
          if (typeof then === 'function') {
            then.call(
              val,
              function(val) {
                res(i, val);
              },
              reject
            );
            return;
          }
        }
        args[i] = val;
        if (--remaining === 0) {
          resolve(args);
        }
      } catch (ex) {
        reject(ex);
      }
    }

    for (var i = 0; i < args.length; i++) {
      res(i, args[i]);
    }
  });
};

整个方法也是返回了一个实例化的Promise对象,我们来看起回调参数:

if (!arr || typeof arr.length === 'undefined')
      throw new TypeError('Promise.all accepts an array');

首先对传入的参数做了判断,若未传入参数或传入的参数没有length属性,则会抛出异常。也就是说,传入的参数可以不为数组,而是一个类数组对象! 接下来:

 var args = Array.prototype.slice.call(arr);

将传入的参数转化为真正的数组,保存在args变量中。

if (args.length === 0) return resolve([]);

若传入的数组长度为0,则返回Promise对象的值为空数组。

var remaining = args.length;

将数组的长度保存在remaining变量中。 接下来定义了一个内部函数res,我们先看是如何调用的:

for (var i = 0; i < args.length; i++) {
      res(i, args[i]);
}

遍历数组,将数组下标与数组元素作为参数,调用res函数。来看看res函数具体做了什么:

try {
  if (val && (typeof val === 'object' || typeof val === 'function')) {
       ...
  }
  args[i] = val;
  if (--remaining === 0) {
    resolve(args);
  }
} catch (ex) {
    reject(ex);
}

忽略第一个条件判断,将val赋值给与其下标对应的数组元素,也就将原来的值覆盖掉,这两个值难道不是原谅就是同一个吗?我们可以猜测,上一个一定对val值做了处理。 每次讲remaining变量的值自减1,如果最后值等于0,也就是遍历完成,调用resolve(args),由此可知,返回完成状态的Promise对象的值为一个数组,其数组元素为处理后传入的数组元素,并且可以知道,其数组元素的顺序并没有发生改变! 若抛出异常,则调用reject,将原因作为参数,与我们知道的一致,任意一个被拒绝,返回的Promise对象的值为单一的拒绝原因,而非数组! 再来看第一个条件判断,我们可以思考下,什么样的值才会需要做处理呢?我们调用all方法,最可能传入的值是不是Promise对象?当然还有thenable对象,由此可知,只有这些值才会被处理!

if (val && (typeof val === 'object' || typeof val === 'function')) {
  var then = val.then;
  if (typeof then === 'function') {
    then.call(
      val,
      function(val) {
        res(i, val);
      },
      reject
    );
  return;
  }
}

判断val是对象或函数类型,再判断其then方法是否是函数类型,这些判断在验证就是我们在第一篇中所说的thenable类型。若是thenable类型,则调用val的then方法,以val为this,完成回调为递归调用res函数,知道val不为thenable类型,最后结束此次调用。

不知道大家有没有疑问,反正我当时是有,resolve(args)调用的位置,为什么不是在这:

for (var i = 0; i < args.length; i++) {
   res(i, args[i]);
}
resolve(args);

难道不是在数组遍历完成再调用就可以了吗?其实要注意的是,res函数中调用了then方法,而then方法是异步执行的!所以要确保调用resolve(args)前,所有的Promise状态已经改变!

至此,Promise-Polyfill的源码就分析完毕了。

传送门: Promise-Polyfill源码解析(1) Promise-Polyfill源码解析(2)