Promise-Polyfill源码解析(2)

在上篇文章Promise-Polyfill源码解析(1)详细分析了Promise构造函数部分的源码,本篇我们继续分析剩下的源码。
本篇我们重点分析then方法,让我们回忆下then方法的使用方式:首先这个方法属于每个Promise对象,这说明then方法应该定义在Promise的原型链上;然后这个方法接收两个回调函数,如果Promsie的状态为已完成,则执行第一个回调,状态为被拒绝,则执行第二个回调,这个说明then方法会等待Promise状态改变才会去执行回调;最后then方法可以链式调用,如下:

Promise.resolve().then(function() {
  // ...
}, function() {
  // ...
}).then(function() {
  // ...
}, function() {
  // ...
});

了解了以上,我们来看then方法的源码:

Promise.prototype.then = function(onFulfilled, onRejected) {
  // @ts-ignore
  var prom = new this.constructor(noop);

  handle(this, new Handler(onFulfilled, onRejected, prom));
  return prom;
};

正如我们所猜想的,then方法定义在Promise的构造函数上,每个Promise对象可以共享该方法。其接收两个参数onFulfilled、onRejected。具体实现也非常简洁,只有三行代码,先来看第一行:

var prom = new this.constructor(noop);

这句代码用new操作符实例化了一个对象,并保存在prom变量中。new操作符的右边一定是个构造函数,this指向当前Promise对象,其constructor属性指向构造函数,所以this.constructor指向Promise构造函数。我们知道,Promise构造函数的参数为一个函数,这里传入了noop,noop是什么?我们找到其定义:

function noop() {}

我们发现noop只是个空函数。再来看最后一行代码:

return prom;

返回了prom对象,也就是说,then方法最后返回了一个Promise对象,这也就是then方法可以链式调用的原因所在!
有个疑问,为什么不直接返回this,而是返回新创建的Promise对象呢?其实是因为Promise的状态改变时单向的,且只能改变一次。
然后重点来看下第二行代码:

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

调用了handle函数,先不管handle做了什么,我们先关注其第二个实参:

new Handler(onFulfilled, onRejected, prom)

其实例化了Handler对象,参数为then方法的两个参数和prom对象,我们来看下其具体实现:

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

Handler构造函数将传入的参数分别赋值给实例对象的onFulfilled、onRejected、promise属性,其中对onFulfilled和onRejected做了处理,若不是函数类型,则赋值为null。这说明,我们传入给then方法的两个参数可以不为函数类型,其内部会调整为null。
明白了第二个参数,我们来看handle函数具体做了什么:

function handle(self, deferred) {
  while (self._state === 3) {
    self = self._value;
  }
  if (self._state === 0) {
    self._deferreds.push(deferred);
    return;
  }
  self._handled = true;
  Promise._immediateFn(function() {
    var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
    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);
  });
}

首先是一个while循环:

 while (self._state === 3) {
    self = self._value;
  }

self指的是当前Promise对象,如果self._state的值为3,则将self._value赋值给self。我们在上篇文章分析过,_state属性值为3,则说明_value值为一个Promise对象。那么这个循环的结果就是,直到_value属性值不为Promise对象,为什么要这么处理呢?我们来看下规范是怎么说的:
如果 x 为 Promise,则使promise接收x的状态

  • 如果 x 处于pendding,promise需要保持为pendding状态直至x被解决或拒绝
  • 如果 x 处于fulFilled,用相同的值执行 promise
  • 如果 x 处于rejected,用相同的据因拒绝 promise
    总结起来就是,如果_value属性值为Promise对象,则结果取决于嵌套最内层Promise的状态。
    接下来是一个条件判断:
 if (self._state === 0) {
    self._deferreds.push(deferred);
    return;
  }

如果self._state属性为0,则将deferred压入self._deferreds数组,并结束此次函数调用。其中deferred为传入的Handler实例对象,我们在上篇里分析过,_state属性值为0表示Promise的状态为pendding,我们可以猜测到,状态为pedding,也就是Promise的状态并未改变,then方法不知道要执行哪个回调,所在要先保存。那么为什么是保存在一个数组里,而不是保存在一个变量里,难道有很多个?其实还真可能有很多个,因为then方法可以被多次调用:


image.png

可以看到,每个then方法的回调都被执行了。
再来看下面的代码:

self._handled = true;

上篇文章也分析过,_handled属性用来标记Promise是否被处理,这里将其赋值为true,说明当前Promise对象已经被处理了。
最后来看最后一段代码:

Promise._immediateFn(function() {
   ...
});

调用了Promise._immediateFn方法,并传入了一个回调函数。先来看Promise._immediateFn的定义:

// Use polyfill for setImmediate for performance gains
Promise._immediateFn =
  (typeof setImmediate === 'function' &&
    function(fn) {
      setImmediate(fn);
    }) ||
  function(fn) {
    setTimeoutFunc(fn, 0);
  };

这里判断setImmediate是否是函数类型,成里则赋值为function(fn) { setImmediate(fn) },否则赋值为function(fn) { setTimeoutFunc(fn, 0) },其中setTimeoutFunc是setTimeout的别名:

var setTimeoutFunc = setTimeout;

setImmediate是Node.js里的global对象的属性,而setTimeout是浏览器环境里window对象的属性,所以Promise._immediate是兼容两个环境所做处理的代码。为什么要再包一层闭包呢?应该是兼容参数的数量。
到这我们也明白了,then方法的回调是异步执行,其实更具体是在micro队列中,这里我们就不展开了。
回到Promise._immediateFn的回调参数:

var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;

上篇文章分析过,self._state属性值为1表示Promise的状态为已完成,为2表示状态为被决绝。那么这句代码的意思是,根据Promise的状态,将then方法的完成回调或决绝回调赋值给cb变量。
再来看下面的条件判断:

if (cb === null) {
  (self._state === 1 ? resolve : reject)(deferred.promise, self._value);
  return;
}

cb变量为null,也就是我们传入给then方法的参数不是函数类型,这里会根据Promise的状态执行resolve或reject函数,并结束此次调用。注意传入的参数,deferred.promise和self._value,也就是说,用Promise的值去改变在then方法内创建的Promise对象的状态。总结起来就是,若then方法未传入对应的回调,那么Promise的值会被传递到下一次then方法中:


image.png

再来看最后一段代码:

var ret;
try {
  ret = cb(self._value);
} catch (e) {
  reject(deferred.promise, e);
  return;
}
resolve(deferred.promise, ret);

忽略try..catch,核心是这样的:

var ret = cb(self._value);
resolve(deferred.promise, ret);

将self._value作为参数,调用cb函数,返回值保存在ret变量中,再以ret变量为参数调用resolve函数。这里的意思就是,将cb函数的返回值作为Promise的值传递给下一个then方法:


image.png

当然,若抛出异常,则将原因作为Promise的值,传递给下一个then方法:

reject(deferred.promise, e);
return;

至此,Promise源码的核心部分已经分析完了,我们可以发现,阅读源码可以了解Promise的内部的工作机制,当出现问题时,我们也能快速定位原因。鼓励大家去阅读源码!
当然还有catch、all、race等方法,将在下一篇文章继续分析。

你可能感兴趣的:(Promise-Polyfill源码解析(2))