DD DOG

Promise 源码浅析

  • JackLiu的头像
    JackLiu | 2 个月前
  • 源码
photo

Promise 源码浅析

Promise 简介

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件,更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。在浏览器端,大部分已经支持 Promise,但仍有部分低版本浏览器不支持,那就需要使用 Promise 的兼容实现库,比如 Promise-ployfill、babel-polyfill 等。

本文以 Promise-ployfill 的源码为例,浅析 Promise。

promise-ployfill

主文件路径为 promise-polyfill/src/index.js

Promise 构造函数

在 ECMAScript 6 中 Promise 是一个全局对象,也可以理解为 Promise 是一个类。而创建 Promise 对象的实例,要使用 Promise 对象的构造函数。

/**
 * @constructor
 * @param {Function} fn
 */
function Promise(fn) {
  // 只能通过 new 来实例化 Promise 对象
  if (!(this instanceof Promise))
    throw new TypeError('Promises must be constructed via new');
  if (typeof fn !== 'function') throw new TypeError('not a function');
  /** @type {!number} */
  this._state = 0;
  /** @type {!boolean} */
  this._handled = false;
  /** @type {Promise|undefined} */
  this._value = undefined;
  /** @type {!Array<!Function>} */
  this._deferreds = [];

  doResolve(fn, this);
}

之所以只能通过 new 来实例化 Promise 对象,是因为js的对象构造器,实际上是一个函数。用new方式会创建对象的一个新实例,构造函数内对this的引用会指向新实例本身。如果不用new,相当于直接调用函数。构造函数内对this的引用会指向调用构造函数时的上下文环境。

doResolve

在 doResolve 函数中执行 fn 函数,fn 函数的参数是两个回调函数。

function doResolve(fn, self) {
  // done 为了让 resolve, reject 只执行一次
  var done = false;
  try {
    fn(
      function(value) {
        if (done) return;
        done = true;
        resolve(self, value);
      },
      function(reason) {
        if (done) return;
        done = true;
        reject(self, reason);
      }
    );
  } catch (ex) {
    if (done) return;
    done = true;
    reject(self, ex);
  }
}

到这里可能就不好理解了,我们先来看看一般如何实例化 Promise。

var promise = new Promise(function(resolve, reject) {
  // 异步处理回调函数
  // 处理结束后调用resolve 或 reject方法
  if (success){
    resolve(value);
  } else {
    reject(error);
  }
})

实例化 Promise 的时候传入一个匿名函数,这个匿名函数有两个参数 resolve、reject。这两个参数则是代表回调函数,分别在成功、失败时调用。 于是,结合之前的代码,我们可以知道,doResolve 函数,执行 fn 方法时的两个回调在实际运行时相当于以下代码。


...
if (success) {
  if(done) return
  done = true
  resolve(self, value)
} else {
  if (done) return
  done = true
  reject(self, errorc)
}
...

Promise 内部方法 resolve、reject

那么现在重点异步到源码中 resolve、reject 的实现。


// 一个 Function.prototype.bind 的 Polyfill
// 可以不污染全局 Function 原型方法
function bind(fn, thisArg) {
  return function() {
    fn.apply(thisArg, arguments);
  };
}

function resolve(self, newValue) {
  try {
    // 判断 newValue 不能是自身,否则死循环
    if (newValue === self)
      throw new TypeError('A promise cannot be resolved with itself.');
    // 判断 newValue 存在,且类型为 object 或 function
    // 这里主要是看 newValue 是否还是一个 Promise
    if (
      newValue &&
      (typeof newValue === 'object' || typeof newValue === 'function')
    ) {
      var then = newValue.then;
      // 如果是 Promise
      if (newValue instanceof Promise) {
        self._state = 3;
        self._value = newValue;
        finale(self);
        return;
      } else if (typeof then === 'function') {
        // 如果 newValue.then 是 function 则认为 newValue 是 Promise
        // 修正 this 指向,递归执行 doResolve 函数,直到 newValue 是一个普通值
        doResolve(bind(then, newValue), self);
        return;
      }
    }
    // 当 newValue 是普通值时,设置 promise 的状态与值
    self._state = 1;
    self._value = newValue;
    finale(self);
  } catch (e) {
    // 错误处理
    reject(self, e);
  }
}

function reject(self, newValue) {
  self._state = 2;
  self._value = newValue;
  finale(self);
}

function finale(self) {
  if (self._state === 2 && self._deferreds.length === 0) {
    Promise._immediateFn(function() {
      if (!self._handled) {
        Promise._unhandledRejectionFn(self._value);
      }
    });
  }

  for (var i = 0, len = self._deferreds.length; i < len; i++) {
    handle(self, self._deferreds[i]);
  }
  self._deferreds = null;
}

在 resolve 函数中判断 newValue 类型时,用了 instanceof 方法。又判断了 newValue的 then 类型是否是 function,可能会感觉到疑惑。其实这里主要是考虑到 newValue 是其他框架的 Promise(比如:global.Promise,nodejs 内部的 Promise 实现)的构造实例。所以 重新调用 doResolve,继续执行当前类型的Pormise then方法。

resolve 函数最终赋值给 self 对象的_value, _state不同值,在 finale 方法中处理。finale 方法将 _state === 2 ,即 reject 函数的

到目前,可以知道 _state的值有4种情况 ,枚举出来:

  • _state === 0 // pending
  • _state === 1 // fulfilled,执行了resolve函数,并且_value instanceof Promise === false
  • _state === 2 // rejected,执行了reject函数
  • _state === 3 // fulfilled,执行了resolve函数,并且_value instanceof Promise === true

核心逻辑 — handle 方法

function handle(self, deferred) {
  // _state === 3 说明 _value 是 Promise 对象
  // self._value => self,接下来处理新 Promise
  while (self._state === 3) {
    self = self._value;
  }

  // self._state=== 0 说明还没有执行 resolve || reject 方法
  // 此处将 handle 挂起
  if (self._state === 0) {
    self._deferreds.push(deferred);
    return;
  }
  self._handled = true;
  
  // 通过事件循环异步来做回调的处理
  Promise._immediateFn(function() {
    // deferred.promise 是返回的新 Promise 对象
    // 这里调用下一个 Promise 对象的 then 方法的回调函数
    // 如果当前 Promise resolve 了,则调用下一个 Promise 的 resolve方法,反之,则调用下一个 Promise 的 reject 回调
    // 如果当前 Promise resolve 了,则调用下一个 Promise 的 resolve方法
    // cb回调方法:如果自己有 onFulfilled || onRejected 方法,则执行自己的方法;如果没有,则调用下一个 Promise 对象的 onFulfilled || onRejected
    var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
    // 自己没有回调函数,进入下一个 Promise 对象的回调
    if (cb === null) {
      (self._state === 1 ? resolve : reject)(deferred.promise, self._value);
      return;
    }
    // 自己有回调函数,进入自己的回调函数
    var ret;
    try {
      ret = cb(self._value);
    } catch (e) {
      reject(deferred.promise, e);
      return;
    }
    resolve(deferred.promise, ret);
  });
}

Promise._immediateFn =
  (typeof setImmediate === 'function' &&
    function(fn) {
      setImmediate(fn);
    }) ||
  function(fn) {
    setTimeoutFunc(fn, 0);
  };

/**
 * @constructor
 */
function Handler(onFulfilled, onRejected, promise) {
  this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
  this.onRejected = typeof onRejected === 'function' ? onRejected : null;
  this.promise = promise;
}

self._state === 3,说明当前 resolve(promise)方法回传的值类型为 Promise 对象。 即 self._value instanceOf Promise === true, 将 self=self._value,即当前处理变更到了新的 Promise 对象上 ,如果当前 promise对象内部状态是 fulfilled 或者 rejected,则直接处理 onFulfilled 或者 onRejected 回调;如果仍然是 padding 状态,则继续等待。

静态方法 resolve

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 实例化对象, value 作为 resolve 回调的参数。

静态方法 reject

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

返回一个新的 promise 实例化对象, value 作为 reject 回调的参数。

静态方法 race

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

};

race 方法的参数 values(Promise 数组),利用for 循环执行每个 Promise 的 then 方法,doResolve方法内部 done 变量控制了对 resolve reject 方法只执行一次的处理,保证了最快执行的 Promise 能做 resolve reject 的回调,从而达到了多个Promise race 竞赛的机制,谁跑的快执行谁。

静态方法 all

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) {
      // 如果 val 是 Promise 对象的话,则执行 Promise,直到 resolve 了一个非 Promise 对象
      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;
          }
        }
        // 用当前resolve || reject 的值重写 args[i] {Promise} 对象
        args[i] = val;
        // 直到所有的 Promise 都执行完毕,则 resolve all 的 Promise 对象,返回 args 数组结果
        if (--remaining === 0) {
          resolve(args);
        }
      } catch (ex) {
        reject(ex);
      }
    }

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

all 等待所有的 Promise 都执行完毕,才会执行 Promise.all().then()回调,只要其中一个出错,则直接进入错误回调,因为对于所有 all 中 promise 对象 reject 回调是公用的,利用doResolve内部的 done变量,保证一次错误终止所有操作。

但是对于 resolve 则不一样, resolve 回调函数通过 res 递归调用自己,从而保证其值_value不为 Promise 类型才结束,并将_value 赋值到 args 数组,最后直到所有的数组Promise都处理完毕由统一的 resolve 方法结束当前的 all 操作,进入 then 处理流程。


附 Promise 流程图

promise

参考 从使用角度渐进式剖析Promise源码

源码