Promise 超详细源码分析,保证你能看懂

1,612 阅读14分钟

这个周编码过程中使用了NodeJs去处理图片,因为图片处理的接口全都是Promise,就导致了我的程序中充满了Promise的嵌套返回,then的结果中有返回了另一个Promise,另一个Promise中一系列中的then中有的又会返回新的Promise,这一度让我变得混乱。

理清楚后,不禁对Promise为什么能够如此神奇产生了好奇,于是去阅读了它的源码,才发现有些习以为常的功能背后的设计的奇妙。

Promise 使用

ES6提供Promise构造函数,我们创造一个Promise实例,Promise构造函数接收一个函数作为参数,这个传入的函数有两个参数,分别是两个函数 resolvereject作用是,resolve将Promise的状态由未成功变为成功,将异步操作的结果作为参数传递过去;相似的是reject则将状态由未失败转变为失败,在异步操作失败时调用,将异步操作报出的错误作为参数传递过去。 实例创建完成后,可以使用then方法分别指定成功或失败的回调函数,比起f1(f2(f3))的层层嵌套的回调函数写法,链式调用的写法更为美观易读

我们在创建一个新的Promise的时候需要传入一个函数作为参数,这个传入的函数有两个参数,分别是两个函数,resolve以及reject,函数体内部执行异步操作,然后根据操作的结果是否为需要的结果调用不同的函数,如果获得了正确的返回,就调用resolve函数,并将你需要返回的结果作为参数,如果没有获取正确的返回,就调用reject函数,并将错误信息作为参数,这样外部就可以获取错误信息了。举个简单的例子:

const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        const num = Math.random();
        num > .5 ? resolve(`success:${num}`) : reject(`fail:${num}`);
    }, 1000);
});

promise.then((resolveVal) => {
    console.log(val);
}).catch(rejectVal => {
    console.log(rejectVal)
})

这个例子创建了一个Promise,在一秒钟之后生成一个随机数,如果随机数大于0.5就返回调用resolve,如果小于0.5,如果小于0.5就调用reject。

当执行了resolve时,Promise的状态会变为resolved,并会执行then中传入的函数,其实就相当于then中传入的函数会被当作resolve函数执行。同理,当执行了reject时,Promise的状态会变为Rejected,这个时候会利用catch中传入的函数作为reject函数去执行。

总的来说,then和 catch时为Promise加载了两个resolve和reject时具体需要执行的函数。

then的规则

  • then方法下一次的输入需要上一次的输出

  • 如果一个promise执行完后 返回的还是一个promise,会把这个promise 的执行结果,传递给下一次then中。也就是说如果你在PromiseA的then中返回了PromiseB,那么PromiseB的结果会作为PromiseA下一步then的入参。

    const promiseA = new Promise((resolve, reject) => {
        ...
    });
    const promiseB = new Promise((resolve, reject) => {
        
    })
    promiseA.then((resolveA) => {
        return promiseB;
    }).then((resolveB) => {
        
    })
    

    这种情况下第二个then的参数就是promise的返回结果。

  • 如果then中返回的不是Promise对象而是一个普通值,则会将这个结果作为下次then的成功的结果

  • 如果当前then中失败了 会走下一个then的失败

  • 如果返回的是undefined 不管当前是成功还是失败 都会走下一次的成功

  • then中不写方法则值会穿透,传入下一个then

  • then函数中的return val 与Promise resolve(val)相同

举例说明:

//example1
promise1 = new Promise((resolve) => {
    setTimeout(() => {
        resolve('promise1');
    }, 1000)
})

当参数是一个非promise的时候,1秒后promise的状态立即变成resolve,并执行then里面的事件.

//example2
promise1 = new Promise((resolve) => {
    setTimeout(() => {
        promise2 = new Promise((resolve, reject) => {
            resolve('promise2');
        })
        resolve(promise2);
    }, 1000)
})

当参数是另一个promise的时候,这时promise1的状态由promise2来决定,什么时候promise2变化了状态,promise1的状态也会相应的变化,并且状态保持一致.

//example3
promise1 = new Promise((resolve) => {
    resolve('promise1');
})
promise2 = promise1.then((data) => {
    return 'promise2';
})

当回调函数里面直接return一个非promise,和上面的example1一样,当前的promise2状态变为resolve。相当于执行了(resolve('非promise'))

//example4
promise1 = new Promise((resolve) => {
    resolve('promise1');
})
promise2 = promise1.then((data) => {
    promise3 = new Promise((resolve, reject) => {
        resolve('promise3');
    })
    return promise3;
})

当回调函数里面直接return一个promise3,和上面example2一样,当前promise2的状态依赖于primise3,相当于执行了(resolve(promise3))

//example5
promise1 = new Promise((resolve) => {
    resolve('promise1');
})
promise2 = promise1.then((data) => {
    console.log( iamnotundefined );
})

当回调函数里面代码报错了,并且没有被catch到的,当前promise状态变为reject.(异步的error代码catch不到,不会影响promise状 态变化)

catch的规则

  • 执行reject的时候会调用catch
  • 前面出现任何没有被处理的错误时会执行catch

源码分析

Promise源码地址

下面的注释说明了对Promise状态的可能值:

0 - 等待中 1 - 满足条件 (值为 _value) 2 - 拒绝条件 (值为 _value) 3 - 采用了另一个Promise的状态和值

一旦状态值不为0, 那么这个Promise将不可以被修改.

在正式声明Promise之前,为了减少对try catch在代码中显示,定义了几个工具函数,

工具函数

function noop() {} //空回调函数,用于then

// States:
//
// 0 - pending
// 1 - fulfilled with _value
// 2 - rejected with _value
// 3 - adopted the state of another promise, _value
//
// once the state is no longer pending (0) it is immutable

// All `_` prefixed properties will be reduced to `_{random number}`
// at build time to obfuscate them and discourage their use.
// We don't use symbols or Object.defineProperty to fully hide them
// because the performance isn't good enough.


// to avoid using try/catch inside critical functions, we
// extract them to here.
var LAST_ERROR = null;
var IS_ERROR = {};
function getThen(obj) {
  try {
    return obj.then;
  } catch (ex) {
    LAST_ERROR = ex;
    return IS_ERROR;
  }
}

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

声明

module.exports = 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状态依赖另一个Promise时才有用,也就是resolve了一个Promise
  this._state = 0;//当前Promise的状态
  this._value = null;//当前Promise的resolve的值
  this._deferreds = null;//当_deferredState变为成功,也就是大于2时,执行的回调函数数组
  if (fn === noop) return;
  doResolve(fn, this);
}

函数Promise接受一个函数作为其参数,必须通过new来创建。

初始化 _deferredState 和 _state为 0, _value和_deferreds为null, 如果传入函数是一个空函数,那么直接返回。 正常情况下,进入 doResolve 开始流程。

doResolve

function doResolve(fn, promise) {
  var done = false;
    //注意,fn就是我们new Promise时传入的(resolve, reject)=> { if success resolve else reject},tryCallTwo会将第二个参数传给resolve,将第三个参数传给reject,这样当我们在声明的Promise中调用resolve时实际上调用的时trayCallTwo的第二个参数。
  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);
  }
}

这里同步的直接调用传入的函数,讲两个函数 (即外部编写的resolve和reject)作为参数传入 , 调用完成后检查下是否是没完成的情况下出错了,如果是直接reject. 针对传入的resolve函数和reject函数,等待结果后,如果尚未完成,则通过本文件中定义的resolve和reject来继续流程.

注意tryCallTwo函数的第二个和第三个参数都是一个函数,这两个函数的参数其实就是我们new一个新的Promise的时候传入的两个函数而这段代码中的resolve和reject则是Promise内部定义的方法

resolve & reject

function resolve(self, newValue) {
 // 一个Promise的解决结果不能是自己 (因为根据一开始我们提到的then的原则中,如果你返回了一个新的Promise,那么当前Promise的状态就会依赖于新的Promise,如果自己依赖自己,那么就会一直循环依赖并处于pending状态)
    
// 这里的newValue其实就是我们定义的Promise实例中resolve的参数,依照前面的使用方法,它可以是字符串,数组等,也可以是另一个一个Promise
  if (newValue === self) {
    return reject(
      self,
      new TypeError('A promise cannot be resolved with itself.')
    );
  }
  // 当新的值也就是resolve函数的参数存在并且类型是对象或者函数的时候
  // typeof Promised实例 === 'object'
  // 也就是说这个if成功的条件是resolve了一个对象,函数或者Promise实例
  if (
    newValue &&
    (typeof newValue === 'object' || typeof newValue === 'function')
  ) {
    var then = getThen(newValue);  // let then = newValue.then
    if (then === IS_ERROR) { //IS_ERROR就是上面所说的工具中声明的,就是一个空对象,只有当return newValue.then发生异常时才会为IS_ERROR
      return reject(self, LAST_ERROR);
    }
    if (
      then === self.then &&
      newValue instanceof Promise // 如果resvole的是一个Promise,并且这个Promise的then与当前Promise的then相同时(这个then一般是相同的,都是定义在Promise的原型上的),直接就用当前Promise的结果为最终结果。
    ) {
      self._state = 3; //状态3说明采用另一个Promise作为结果
      self._value = newValue;
      finale(self); // 那么采用这个Promise的结果
      return;
    } else if (typeof then === 'function') {//走到这里就说明,resolve了一个有then方法的对象(或者一个Promise,并且和当前Promise的then不同,这种情况比较少见)
      doResolve(then.bind(newValue), self); // 递归调用doResolve,刚才我们也看了,doResolve的第一个参数是我们new Promise时传入的函数,第二个参数是当前的Promise实例的指针,也就是在递归中执行到这个then函数时,其内部的this指向的是这里的newValue,这里self没变
      return;
    }
  }
  //上面的if都没有return,才会走到这里,到了这里就说明,resolve了普通的值,比如数字,字符串,标记完成,进入结束流程
  self._state = 1; //状态1说明进入成功状态
  self._value = newValue;
  finale(self);
}

function reject(self, newValue) {
  //设置reject状态和理由
  self._state = 2;
  self._value = newValue;
  if (Promise._onReject) {
    Promise._onReject(self, newValue); //过程回调通知
  }
  finale(self); //结束 
}
//这个函数只有self的状态不为0的时候才会执行,这个时候执行handle就会执行传入的第二个参数,也就是_deferreds,_deferredState为1表明_deferreds是一个Handler,_deferredState为2说明_deferreds是Handler的数组,全都执行完了,再把_deferreds赋值为空
//也就是说finale是把所有的Handler执行一次
// 每个Hander都是通过then方法传入的,then方法有两个参数,分别是onFulfilled,onRejected,也就是resolve时应该执行的函数和reject时应该执行的函数
// 如果是_state为3的时候执行finale,其实就是先把当前Promise通过then创建的Handler挂到了它所依赖地Promise实例的_deferreds上去
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;
  }
}

然就冒出来之前未曾触及到的_deferredState和_deferreds的使用,他们是什么用的? 在回答这些之前,我们先回想下在Promise的时候中,当创建并返回了promise之后,下面的操作就是then来获取结果了 (当然也包括catch), 那么对于这个then的实现我们先来看一下:

then

//then的参数是两个函数,onFulfilled是我们resolve时调用的回调函数,onRejected是我们reject时调用的回调函数
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;
};

其中的safeThen和then的用法基本一致,都是创建了一个异步的空回调res,然后使用onFulfilled, onRejected和res来创建 Handler。那么核心就在handle这个函数上了:

function handle(self, deferred) {
  //self._state === 3说明self指向的Promise实例的状态依赖另一个Promise实例,这个实例就是self._value
  //也就是说通过这个循环,最终self最终会指向一个状态只依赖自己的Promise实例
  //也就是说假设self是p1,它依赖p2的状态,当p1调用handle的时候,并且p2还没有resolve,其实是把p1通过then创建的Handler挂到了p2的_deferreds上去,当p2 resolve的时候依次执行。
  while (self._state === 3) {
    self = self._value;
  }
  if (Promise._onHandle) { // for injection - not in main loop
    Promise._onHandle(self);
  }
  //如果这个状态只依赖自己的Promise实例还没有结果,就把传入的回调函数先保存起来
  // _deferredState 为0表明还没有保存过回调函数,那么就把回调函数赋值给_deferreds,这个时候_deferreds只是一个回调函数
  // _deferredState 为1表明保存过回调函数,那么就把回调函数和原有的保存在_deferreds的回调函数构造成一个数组重新赋值给_deferreds,这个时候_deferreds只是一个回调函数的数组
  // _deferredState 为2表明_deferreds已经是一个回掉函数的数组了,就push就可以了
  // 直到某一次调用handle,self的状态不为0了,才会执行handleResolved
  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);
}

这个函数的参数就是deferred, 那么说明deferred就是Handler, 结合意思,指代的就是延迟的处理。明白点说,就是完成promise之后所需要做的事情。 那么具体的过程是怎么样的呢? 首先判断当前状态是不是依赖于另一个promise, 是的话则通过while等待 然后onHandle只是个提供给外部的进度回调,这里先无视 当状态为0的时候,这里就是设置未来的处理过程了,

如果未来状态没有设置过(0), 那么设置回调(deferred) 为单独回调
如果未来状态设置过了 (1),  那么设置回调 (deferred) 进入回调数组
如果其他状态 (2 +),那么直接进入回调数组。
对状态0情况下的处理这里就返回了。 因为在这个时候,是promise同步执行过来的then, 设置好未来处理的函数过程。 

当状态非0的时候, 就进入了handleResolved,这应该就是完成后处理结束的地方了。 等等,上面只是从then出发进入handle的,那时候应该promise还没有完成,处理完成的调用一定是在别的地方。 通过搜索handle的调用可以看到还有在finale函数中, 这样就和上面连接上了,我们先回顾下什么时候会调用finale呢?

1 状态3 等待其他promise的结果时候 - 这里会进入等待
2 状态1 完成的时候
3 状态2 reject的时候

可以看到只有在promise结束或者依赖其他promise的时候,才会进入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;
  }
}

finale中会将之前放入的deffereds 一一取出 调用handle, 这时state均为非0,直接进入handleResolved, 代码如下:

function handleResolved(self, deferred) {
  asap(function() {
    // self._state 为 1,就执行then函数的第一个参数,就是成功的回调函数,否则执行reject的回调函数
    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调用,如果没有onFulfilled(onRejected失败情况),则直接调用resolve(reject), 如果有则先调用onFulfilled(onRejected失败情况),根据结果来调用resolve(reject)。

等等。。这里的resolve和reject不是在上面的流程中有出现了么?请注意这里resolve和 rejected的promise, 这个promise是在then的时候创建的空promise,也就是意味这什么都不会执行 (直接进入finale 无handle情况)。 所以真正影响这里流程的是 对于deferred.onFulfilled 或者 deferred.onRejected的回调执行,执行完回调 这个promise的执行过程就完成了。

综上, promise的执行过程是这样的

  • 创建Promise
  • 设置需要执行的函数,也就是new Promise是传入的函数
  • 设置完成的回调,也就是then传入的两个函数,第一个是resolve时执行的,一个是reject执行的,如果resolve了一个Promise2,那么当前then创建的Handler会挂载到Promise2上,等到Promise2 resolve了再一起执行。
  • 开始执行函数
  • 根据执行结果选择回调

另外提一句safeThen, safeThen的作用是当调用then的时候环境this已经不是Promise的情况下能够继续安全执行then。

参考文章:

juejin.cn/post/684490…

juejin.cn/post/684490…

www.jianshu.com/p/b63ec30ee…