promise-polyfill 梳理分析【三:内置函数的实现】

359 阅读5分钟

经过上两节的梳理,promise的基本流程和原理也已经理解了,这一节就来看看我们经常使用Promise提供的函数是怎么实现的。

[第一节][github.com/zachrey/zbl…] 第二节

Promise.resolve

可以看到这个函数是直接放在promise对象上的,而不是放在原型链上。

Promise.resolve = function(value) {
  if (value && typeof value === 'object' && value.constructor === Promise) {
    return value;
  }
  return new Promise(function(resolve) {
    resolve(value);
  });
};

首先判断,如果是传入的value是一个promise对象的话,就直接返回了,否则就创建一个新的promise对象,然后直接将promise决策到resolved状态。通过这里也可以与我们平时使用时候的预期是一样的,Promise.resolve会创建并且返回一个promise对象。

Promise.reject

相比 resolve 它不存在状态转移,不需要根据入参来转移状态,这个我们上一节已经说过了。所以它的实现也就是新创建一个promise然后去将它决策到rejectd状态。

Promise.reject = function(value) {
  return new Promise(function(resolve, reject) {
    reject(value);
  });
};

Promise.race

首先了解一下这个函数的作用,race 函数返回一个 Promise,它将与第一个传递的 promise 相同的完成方式被完成。它可以是完成( resolves),也可以是失败(rejects),这要取决于第一个完成的方式是两个中的哪个。我还在mdn中看到如下定义:

如果传的迭代是空的,则返回的 promise 将永远等待。

现在看看 promise-polyfill 是怎么实现的。

Promise.race = function(arr) {
  return new Promise(function(resolve, reject) {
    if (!isArray(arr)) {
      return reject(new TypeError('Promise.race accepts an array'));
    }

    for (var i = 0, len = arr.length; i < len; i++) {
      Promise.resolve(arr[i]).then(resolve, reject);
    }
  });
};

首先,可以看到,直接就创建了一个promise对象,然后返回了。重点在于构造函数传入的resolver方法。

  1. 判断传入参数是否为数组,如果不是则报错。注意这里,第一,并没有对数组的类型进行检查。第二,并没有对空数组进行检查。通过以上两点,就可以知道[1, 2, new Promise(() => {...})]这样的传入的参数是可以的,只是会立马被决策为resolved。我们也可以传入一个空数组,这里也符合官方的定义,空数组永远不会决策。所以上面两点不建议使用,但是需要知道。

  2. 遍历的去使用Promise.resolve去执行数组的每一项,上面说过,Promise.resolve肯定一定会返回一个promise对象,如果碰到数组里面为其他类型,也就会新创建一个promise对象,如过不是就直接将这个传入的promise返回。

  3. 使用Promise.resolve后,就去调用这个对象的then方法,去处理成功和失败。看到这里,只要数组里面的某一个promise决策了,那么就会调用resolve或者reject来导致race函数返回的promise也跟着决策,达到了第一个成功或者失败就决策的定义。

Promise.all

这个函数也如同race一样,会返回一个promise,会将传入的数组里面的所以同步或者异步任务全部成功决策后才会将这个promise决策。

Promise.all = function(arr) {
  return new Promise(function(resolve, reject) {
    if (!isArray(arr)) {
      return reject(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]);
    }
  });
};
  1. 首先判断传入的参数是否为数组,不是则报错。
  2. 兼容传入的参数,保证为数组,但是这里与race不一样,会对空数组进行处理,如果为空,则直接决策到resoled,传入一个空数组为决策结果。
  3. 遍历数组使用res函数进行处理,res接受一点下标和当前遍历的数组元素。

目光转向res函数:

  1. 使用try-catch包裹整个函数,只要其中一步出现了错误,立马调用reject将当前返回的promise决策到rejectd状态。
  2. 判断当前数组元素是否是一个兼容的promise对象,也就是判断是否为对象会在方法,并且有then函数,最终调用该值的then方法,将res函数作为onResolved传入,注意这里有保留传入i,之后这个val决策到了resolved之后好将对应的值赋值。然后将reject函数作为onRejectd传入,所以只要数组中某一箱出现了错误,那么就导致当前返回的promise会决策到rejectd状态。
  3. 如果res传入的val不是promise对象,那么就直接将值放到指定下标地址,这里是再次利用到了args数组。args[i] = val;
  4. 将标志位减一,--remaining。然后在判断标志位是否归零,如果是就调用resolve去将返回的promise决策到resolved状态

Promise.prototype.finally

源码中Promise.prototype['finally'] = finallyConstructor;,这一句代码将一个函数赋值给了finally,所以目光就转向它。

function finallyConstructor(callback) {
  var constructor = this.constructor;
  return this.then(
    function(value) {
      // @ts-ignore
      return constructor.resolve(callback()).then(function() {
        return value;
      });
    },
    function(reason) {
      // @ts-ignore
      return constructor.resolve(callback()).then(function() {
        // @ts-ignore
        return constructor.reject(reason);
      });
    }
  );
}
  1. 函数接受一个callback函数,先回去到当前promise对象的构造函数。
  2. 函数直接返回了一个then函数调用后的值,之前也讲过then,它执行后是会返回一个promise的,便于链式调用。所以这里finally会返回一个promise
  3. then函数传入的onResolvedonRejectd函数,在内部都是直接使用promise.resolvepromise.reject去决策了,决策传入的是callback的执行后的返回值,但是并没有使用这个返回值,最后还是使用的是当前promise决策后的决策值。可以看到,finally之后还是可以在继续.then().then() ... 去操作。

它的实现主要是在最后去执行了最后一个prormisethen方法,然后传入操作了onResolvedonRejectd函数去进行调用回调。还是比较容易理解的。

小结

梳理分析了,promise对象常用的几个方法,如果哪天使用promise没有相应的方法,相信也是可以能自己写出来的。

到这里,promise-polyfill的源码也就分析完了。希望大家有所收获,强烈建议使用断点进行调试阅读代码。

原文地址