从源码上理解Promise

650 阅读11分钟

熟悉的Promise

说道Promise想必大家并不陌生了吧,支撑起了 JS 异步调用的大旗。所谓的Promise 是一个保存着未来会发生的事件(异步调用)的结果

来看看Promise基本用法

const p1 = new Promise((resolve, reject) => {
    console.log('刚刚进入Promise, 立即执行的代码')
    if(true) {
        resolve('nice !!')
    } else {
        reject('some Error')
    }
})

p1.then(val => {
    console.log(val)
}, err => {
    console.log(err)
})

Promise 接收一个 function 作为参数,而这个 function 又有两个参数 resolvereject
resolve 是将 Promise 的状态从 pedding 改为 fulfilled(已成功)
reject 是将 Promise 的状态从 pedding 改为 rejected (已失败)
下面我们来看一个例子

const p2 = new Promise((resolve, reject) => {
     console.log('进入Promise')
     reject('error 1')
     resolve('resolve 1')
     resolve('resolve 2')
}).then(res => {
    console.log(res, 'then1')
}).then(res => {
    console.log(res, 'then2')
}).catch(err => {
    console.log(err)
})

这个例子从控制台中输出的结果如下

image.png 原因是Promise的状态只要经过 resolve 或者 reject 后 状态都是不可逆的
还是上面的代码我们把 reject('error 1') 去掉看看执行结果 image.png then1 为 resolve 1 一点不奇怪,then2 为undefined 的是因为链式调用的then方法的参数需要前面的then方法中return出结果例如 image.png

Promise是怎么实现的

那么接下来我们来看看Promise内部是怎么实现的(以下的源码采用的是Promise A+)
使用Promise 的时候都是new Promise,显然Promise 是个构造函数,我们先写一个简单的Promise 例子来代入到源码中看看到底经历了什么

const p3 = new Promise((resolve, reject) => {
    console.log('刚刚进入Promise')
    resolve('resolve 1')
    resolve('resolve 2')
    reject('reject error')
}).then(res => {
    console.log(res, 'then1')
    return 'then1 result'
}, err => {
    console.log(err, 'then1 error')
}).then(res => {
    console.log(res, 'then2')
})

首先看到Promise 的构造函数,一个入参 fn,这个fn就是我们传入new Promise的函数体
跳过两个判断可以看到在Promise构造函数中是初始化了Promise的状态,我在代码中标记了注释

function Promise(fn) {
  if (typeof this !== 'object') {
    throw new TypeError('Promises must be constructed via new');
  }
  if (typeof fn !== 'function') {
    throw new TypeError('Promise constructor\'s argument is not a function');
  }
  this._deferredState = 0; //标记的下一个调用 Promise 的状态,后续的then方法链式调用中会用到
  this._state = 0; // Promise的状态 0 pedding; 1 fulfilled; 2 rejected;
  this._value = null; // Promise 的 value
  this._deferreds = null; // 用于下一个调用的 Promise 实例,后续的then方法链式调用中会用到
  if (fn === noop) return; // 这里的noop函数是个空函数,用于判断这个Promise是否是then方法中重新new Promise 是then方法中的 new Promise 就不执行后续直接return
  doResolve(fn, this);
}

doResolve

构造函数中做的事情并不多,一些判断加一些初始化,继续往下走,介绍下参数,

  1. 第一个参数 fn 就是我们传入new Promise的函数体,
  2. 第二个参数 this 是当前 Promise 的实例。
function doResolve(fn, promise) {
  var done = false; // 初始化一个标记的变量
  var res = tryCallTwo(fn, function (value) {
    if (done) return;
    done = true;
    resolve(promise, value);
  }, function (reason) {
    if (done) return;
    done = true;
    reject(promise, reason);
  });
  if (!done && res === IS_ERROR) {
    done = true;
    reject(promise, LAST_ERROR);
  }
}

doResolve() 全程也就是调用了下 tryCallTwo 方法,三个参数

  1. 第一个是 传入new Promise的函数体
  2. 第二个参数是一个function 里面执行了 resolve方法
  3. 第三个参数是一个function 里面执行了reject方法

tryCallTwo

我们看看 tryCallTwo

function tryCallTwo(fn, a, b) {
  try {
    fn(a, b);
  } catch (ex) {
    LAST_ERROR = ex;
    return IS_ERROR;
  }
}

他就是把这个a,b两个参数当做fn的两个参数执行了一遍fn,这个a和b就相当于我们 new Promise 函数体里的 resolve和reject,当我们 new Promise 函数体执行到 resolve或者 reject 的时候,就回执行这个a和b函数 我们代入我们的例子

const p3 = new Promise((resolve, reject) => {
    console.log('刚刚进入Promise')
    resolve('resolve 1')
    resolve('resolve 2')
    reject('reject error')
})

function doResolve(fn, promise) {
  var done = false; // 初始化一个标记的变量
  var res = tryCallTwo(fn, function (value) { // value => 'resolve 1'
    if (done) return; // 第一次调用时done 为 false
    done = true; // 第一次调用后 done设置为true后续的 resolve或者reject都直接return不生效
    resolve(promise, value);
  }, function (reason) {
    if (done) return;
    done = true; // 理由同上
    reject(promise, reason);
  });
  if (!done && res === IS_ERROR) { // 出异常直接走 reject
    done = true;
    reject(promise, LAST_ERROR);
  }
}

这就是为什么 Promise 的状态是不可逆的 再来看看后续的是如何操作的

resolve

  1. 第一个参数 当前Promise 的实例
  2. 第二个参数 当前Promise 的value
function resolve(self, newValue) { // newValue => 'resolve 1'
  if (newValue === self) {
    return reject(
      self,
      new TypeError('A promise cannot be resolved with itself.')
    );
  }
  if (
    newValue &&
    (typeof newValue === 'object' || typeof newValue === 'function')
  ) {
    var then = getThen(newValue);
    if (then === IS_ERROR) {
      return reject(self, LAST_ERROR);
    }
    if (
      then === self.then &&
      newValue instanceof Promise
    ) {
      self._state = 3;
      self._value = newValue;
      finale(self);
      return;
    } else if (typeof then === 'function') {
      doResolve(then.bind(newValue), self);
      return;
    }
  }
  self._state = 1;
  self._value = newValue;
  finale(self);
}

很显然我们的newValue目前是等于 字符串'resolve 1',两个判断皆不符合直接给当前的Promise 实例赋值状态(_state)和值(_value),下一步执行finale

finale

参数 当前Promise 的实例
此时的Promise 实例的 _state 为 1, _value 为 'resolve 1',_deferreds 为 null,_deferredState 为 0,所以这两个判断都跳过

function finale(self) {
  if (self._deferredState === 1) {
    handle(self, self._deferreds);
    self._deferreds = null;
  }
  if (self._deferredState === 2) {
    for (var i = 0; i < self._deferreds.length; i++) {
      handle(self, self._deferreds[i]);
    }
    self._deferreds = null;
  }
}

then

到此Promise已经执行好了,但是不是说Promise是微任务么?目前为止也没有看到哪里和微任务有什么关系,接下来看看then方法的执行

const p3 = new Promise((resolve, reject) => {
    console.log('刚刚进入Promise')
    resolve('resolve 1')
}).then(res => {
    console.log(res, 'then1')
    return 'then1 result'
}, err => {
    console.log(err, 'then1 error')
})
function noop() {} // 源码最上方声明的一个空函数

Promise.prototype.then = function(onFulfilled, onRejected) {
  if (this.constructor !== Promise) {
    return safeThen(this, onFulfilled, onRejected);
  }
  var res = new Promise(noop);
  handle(this, new Handler(onFulfilled, onRejected, res));
  return res;
};

这个判断this 是不是 Promise 的判断暂且先跳过,肯定是等于Promise的
重新new 了一个Promise的实例传入了 noop,这里大家还记得前面Promise构造函数里面的那个判断嘛

 if (fn === noop) return;
 doResolve(fn, this);

所以在这里return后就不会继续执行doResolve那么Promise的状态就不会被更改

handle

继续往下看执行handle方法,这里有个 new Handler ,看看Handler里面做了什么

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

也就是把传入then的参数和新 new 的Promise实例赋值在构造函数里

  1. 第一个参数 当前Promise的实例
  2. 第二个参数 new Handler的实例
function handle(self, deferred) { //self => { _state : 1, _value: 'resolve 1', _deferredState: 0, _deferreds: null }
  while (self._state === 3) {
    self = self._value;
  }
  if (Promise._onHandle) { // 这里初始化是 null
    Promise._onHandle(self);
  }
  if (self._state === 0) { 
    if (self._deferredState === 0) {
      self._deferredState = 1;
      self._deferreds = deferred;
      return;
    }
    if (self._deferredState === 1) {
      self._deferredState = 2;
      self._deferreds = [self._deferreds, deferred];
      return;
    }
    self._deferreds.push(deferred);
    return;
  }
  handleResolved(self, deferred);
}

由于当前的 Promise 的_state 值为 1(在resolve方法中设置的)判断和循环都跳过直接执行 handleResolve

handleResolve

第一个参数 当前Promise 实例 第二个参数 new Handler的实例

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

好了,这里。。终于看到了跟异步调用相关的东西了。搞半天原来在这里。。
这个asap方法是引用的一个外部的包

asap

var asap = require('asap/raw');

这个包主要做的事情就是建立一个微任务,然后把then方法中的事情推到微任务队列等待着队列执行
我们看看这个方法核心的推入微任务队列的代码实现

var BrowserMutationObserver = scope.MutationObserver || scope.WebKitMutationObserver;
var requestFlush = makeRequestCallFromMutationObserver(flush);

module.exports = rawAsap;
function rawAsap(task) {
    if (!queue.length) {
        requestFlush();
        flushing = true;
    }
    // Equivalent to push, but avoids a function call.
    queue[queue.length] = task;
}

function makeRequestCallFromMutationObserver(callback) {
    var toggle = 1;
    var observer = new BrowserMutationObserver(callback);
    var node = document.createTextNode("");
    observer.observe(node, {characterData: true});
    return function requestCall() {
        toggle = -toggle;
        node.data = toggle;
    };
}

判断了 是否是浏览器环境,浏览器环境的Promise用的是 MutationObserver API来推的微任务。。 一路走到这里,一个Promise的常规链路算是走完了。推入微任务队列的任务等待事件循环后执行,执行完成后会执行asap方法的回调函数

function handleResolved(self, deferred) {
  asap(function() {
    var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected; // 显然_state 是等于1的,cb 不为null
    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); // 这里是执行then中的函数体
    if (ret === IS_ERROR) {
      reject(deferred.promise, LAST_ERROR);
    } else {
      resolve(deferred.promise, ret);
    }
  });
}

跳过两个判断后,执行 tryCallOne 方法

tryCallOne

  1. 第一个参数 then 中的回调
  2. 第二个参数 当前 Promise 的Value
function tryCallOne(fn, a) {
  try {
    return fn(a);
  } catch (ex) {
    LAST_ERROR = ex;
    return IS_ERROR;
  }
}

这里可以看到是把当前Promise 的值 当做then方法回调函数的入参来执行后并且将函数的结果 return 然后执行resolve方法,这里的入参注意是 第一个入参 传递给下一个then方法的Promise实例 第二个入参 当前then方法执行后的结果 执行的方法在上面,依旧是跳过判断,赋值了_state,_value后执行finale方法,然而_deferredState 为 0 finale中的代码依旧不执行。到此我们的简单案例已经执行完。

小结

小小的总结一下几点

  1. Promise 函数体中的代码是立即执行的,遇到resolve 或者 reject 才会执行异步的代码
  2. Promise 的状态是不可逆的,遇到的第一个resolve 或者 reject 改变状态后,后面的resolve和reject 不生效
  3. Promise 是的then方法中推入事件到微任务队列
  4. Promise 浏览器端是通过 MutationObserver API 推入的微任务队列
  5. then方法中的回调函数的参数值 是resolve或者reject传入的值,链式调用的then方法回调函数的参数是上一个then中 return 的结果

链式调用的Promise

我们知道,Promise是可以链式调用的,从源码中的then方法就能看出,是 return 了一个新的Promise实例出来,那么我们就可以一直连续的.then下去,那么链式调用的Promise是怎么处理多个then方法里的事件呢?
来看下下面这个Promise题目

const p1 = new Promise((resolve, reject) => {
    resolve('p1 resolve')
}).then(() => {
    console.log('p1 then1')
}).then(() => {
    console.log('p1 then2')
}).then(() => {
    console.log('p1 then3')
})

const p2 = new Promise((resolve, reject) => {
    resolve('p2 resolve')
}).then(() => {
    console.log('p2 then1')
}).then(() => {
    console.log('p2 then2')
}).then(() => {
    console.log('p2 then3')
})

image.png 不知道这个执行结果是不是跟大家想的一样,好了我们还是通过分析下源码来解释下为什么执行的结果是交叉的。
我们这边上面分析的过的代码就不贴源码了,直接方法名称带过,不熟悉的同学可以向上翻着看。

  1. Promise 构造函数内的初始化
this._deferredState = 0
this._state = 0;
this._value = null;
this._deferreds = null;
  1. 执行 doResolvetryCallTwo(这里设置了done = true,目的是让后面的resolve或者reject不能执行)
  2. 执行 resolve 给当前Promise 设置状态和赋值
this._state = 1;
this._value = newValue // 'p1 resolve'
  1. 执行 finale
  2. 执行 then
  3. 执行 new Handlerhandle
  4. 执行 handleResolved这里是调用asap推入then中函数体到微任务队列中,目前微任务队列中[Promise(fulfilled)],一个微任务
  5. 接下来执行的是后面的then方法,而不是asap回调函数中的方法,原因是浏览器的事件循环机制,这个我会再另一篇文章中讲到,总结下来就是浏览器在执行完主站的同步代码后执行当前主栈下的微任务队列中的任务,队列执行采用的是先进先出。
  6. 所以执行的是下一个(第二个)then这里贴出代码看看
Promise.prototype.then = function(onFulfilled, onRejected) {
  if (this.constructor !== Promise) {
    return safeThen(this, onFulfilled, onRejected);
  }
  var res = new Promise(noop);
  handle(this, new Handler(onFulfilled, onRejected, res));// 这里的this指的是第一个then方法中return 的新的Promise实例
  return res;
};
  1. 执行 then
  2. 执行 handle
    1. 第一个参数 上一个(第一个)then中返回的Promise 实例
    2. 第二个参数 new Handler 的实例
function handle(self, deferred) { // _state: 0, _value: null, _deferredState: 0, _deferreds: null
  while (self._state === 3) {
    self = self._value;
  }
  if (Promise._onHandle) {
    Promise._onHandle(self);
  }
  if (self._state === 0) { // 这里会进入判断
    if (self._deferredState === 0) { // 通过判断
      self._deferredState = 1;
      self._deferreds = deferred;
      return;
    }
    if (self._deferredState === 1) {
      self._deferredState = 2;
      self._deferreds = [self._deferreds, deferred];
      return;
    }
    self._deferreds.push(deferred);
    return;
  }
  handleResolved(self, deferred);
}

由于self是上一个then中返回的Promise 实例,_state 是初始化的 0所以会进入判断,凡是进入判断后都会被return,所以不会执行 handleResolved

我们上面知道了 handleResolved 方法中调用的 asap 方法把then中的函数体推入微任务队列,那么既然没有执行也就是后续的then方法中的函数体没有被推入微任务队列,只是在打了个标记(_deferredState)

同理下个then方法执行也是没有推任务到微任务队列只是打了个标记
所以 const p1 这个 Promise执行完只向微任务队列中推入一个微任务
然后同理 执行const p2 这个Promise 也是只有一个微任务推入微任务队列
那么当执行完两个Promise之后只有两个微任务在微任务队列
接下来执行的是微任务队列中的任务(asap的回调) 第一个参数 Promise 的实例(第一个Promise实例) 第二个参数 new Handler实例(里面的promise属性存的第一个then用到的Promise实例)

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 用到的Promise实例而我们上面在执行 handle方法中 _state:0的情况已经给这个实例的 _deferredState 属性打上标记 1
所以resolve方法执行后赋值以及修改状态之后执行的finale方法开始执行了

function finale(self) {
  if (self._deferredState === 1) {
    handle(self, self._deferreds);
    self._deferreds = null;
  }
  if (self._deferredState === 2) {
    for (var i = 0; i < self._deferreds.length; i++) {
      handle(self, self._deferreds[i]);
    }
    self._deferreds = null;
  }
}

再次执行handle方法,这次经过了resolve方法的,所以_state 为1,会跳过判断执行 handleResolved也就是推入微任务队列一个任务,这样我们的微任务队列就是2个任务了(第一个任务执行完后会踢出队列),['p2 resolve', 'p1 then1']按照队列的规则,先进先出,'p1 resolve' 执行完执行的 'p2 resolve',执行的 'p2 resolve'推入的是 'p2 then1',此时队列中的任务['p1 then1', 'p2 then1'] 依次执行'p1 then1',推入 'p1 then2',此时队列中的任务['p2 then1', 'p1 then2'] ...

小结

Promise是在then中推入微任务,链式调用中不会一股脑把所有任务都推入微任务队列,而是只推入第一个then,后面的then方法的函数体都打上标记记录在上一个Promise的属性中,等到上一个微任务执行后再推入链式调用的一个微任务
完整的这个demo的任务队列
['p1 resolve', 'p2 resolve']
['p2 resolve', 'p1 then1']
['p1 then1', 'p2 then1']
['p2 then1', 'p1 then2']
['p1 then2', 'p2 then2']
['p2 then2', 'p1 then3']
['p1 then3', 'p2 then3']
['p2 then3']
[]