Promise源码指南

前端Sharing

共 27845字,需浏览 56分钟

 · 2021-05-01

前言

什么是Promise?

相信许多从事前端的小伙伴们都用过Promise,为了解决异步编程的弊端(地狱回调等问题),ES6提供了一个强大的东西,那就是Promise。Promise是将异步任务转换为同步任务的一个构造函数,通过resolve,reject改变任务的状态,必不可少的then方法用来收Promise的值,这些都是Promise的基本使用。那么Promise是如何处理状态的,又是如何实现resove,reject方法的,又是如何实现链式调用的呢,如果你不知道,那么这篇文章可以帮到你,下面我们一起来解析一下Promise到底是如何实现的,相信看完这篇文章,每个人都可以写出属于自己的Promise方法。

这里引入github上的一份符合Promise A+规范的源码 https://github.com/then/promise

函数对象Promise

我们先来看看src/index.js这个文件

一些必要的定义

// 定义空函数
function noop({}

// 用来存储错误信息
var IS_ERROR = {}

// 获取实例的then
function getThen(obj{
  try {
    return obj.then;
  } catch (ex) {
    LAST_ERROR = ex;
    return IS_ERROR;
  }
}

// 执行then方法回调函数
function tryCallOne(fn, a{
  try {
    return fn(a);
  } catch (ex) {
    LAST_ERROR = ex;
    return IS_ERROR;
  }
}

// 执行Promise构造函数
function tryCallTwo(fn, a, b{
  try {
    fn(a, b);
  } catch (ex) {
    LAST_ERROR = ex;
    return IS_ERROR;
  }
}

Promise构造函数

function Promise(fn{
  // 校验实例
  if (typeof this !== 'object') {
    throw new TypeError('Promises must be constructed via new');
  }
  // 校验Promise的构造函数
  if (typeof fn !== 'function') {
    throw new TypeError('Promise constructor\'s argument is not a function');
  }
  // 存储的实例状态 0代表还未存储 1代表存储了1个 2代表存储了2个
  this._deferredState = 0;
  // Promise的状态  0代表padding  1代表Fulfilled  2代表Rejected 3代表resolve传入的是promise实例
  this._state = 0;
  // Fulfilled的值
  this._value = null;
  // 用来存储调用then后的实例
  this._deferreds = null;
  if (fn === noop) return;
  // 处理Promise的参数
  doResolve(fn, this);
}
//以下先不做解释
newPromise._onHandle = null;
newPromise._onReject = null;
newPromise._noop = noop;

当使用new操作符实例化Promise的时候,必须传入Promise的构造函数fn,否则将抛出错误
然后初始化实例的状态和值 最后调用doResolve方法
下面我们一起来看一下doResolve这个方法

doResolve方法

function doResolve(fn, promise{
  // done防止重复触发
  var done = false;
  // tryCallTwo  用于处理并挂载reolve,reject方法
  // 传入3个参数,Promise构造函数本身,resolve回调,reject回调
  var res = tryCallTwo(fn, function (value{
    if (done) return;
    done = true;
    // 处理resolve方法
    resolve(promise, value);
  }, function (reason{
    if (done) return;
    done = true;
    // 处理reject方法
    reject(promise, reason);
  });
  // 报错则直接reject
  if (!done && res === IS_ERROR) {
    done = true;
    reject(promise, LAST_ERROR);
  }
}

doResolve方法接收两个参数(Promise的构造函数,Promise的实例也就是this)
这里的tryCallTwo扮演了一个重要角色,它执行了构造函数fn,传入三个参数(fn构造函数,resolve回调函数,reject回调函数)
我们回过头来看看tryCallTwo这个函数

// 执行Promise构造函数
function tryCallTwo(fn, a, b{
    //...
    fn(a, b);
    //...
}

这里的a,b就是Promise的resolvereject
执行fn并传入ab
然后resolve和reject又分别执行了resolve方法和reject方法
当实例调用了resolve之后就会执行resolve方法,下面来看看resove这个方法

resolve方法

function resolve(self, newValue{
  // 防止resolve的值传入实例本身
  if (newValue === self) {
    return reject(
      self,
      new TypeError('A promise cannot be resolved with itself.')
    );
  }
  // 这里的if主要用于处理resolve一个Promise
  if (
    newValue &&
    (typeof newValue === 'object' || typeof newValue === 'function')
  ) {
    // 获取Promise的then方法
    var then = getThen(newValue);
    if (then === IS_ERROR) {
      return reject(self, LAST_ERROR);
    }
    // 如果传入的是Promise的实例
    if (
      then === self.then &&
      newValue instanceof newPromise
    ) {
      self._state = 3;
      // 直接把传入的Promise实例挂载到value
      self._value = newValue;
      finale(self);
      return;
    } else if (typeof then === 'function') {  //如果传入带有then方法
      // 把then当作构造函数并且把this指向这个then的对象
      doResolve(then.bind(newValue), self);
      return;
    }
  }
  self._state = 1;
  self._value = newValue;
  finale(self);
}

resolve方法一开始会先判断resolve传入的值,如果resolve传入的是一个「Promise实例」,将会进行以下处理
将实例的状态变为3
将实例的值变为传入的Promise实例
调用finale方法
如果resolve传入的是Promise实例并且「包含then方法」则调用doResolve执行这个实例的构造函数

如果resolve传入的是普通值而不是Promise实例,则做以下处理
将实例的状态变为1
将实例的值变为resolve传入的值
调用finale方法

reject方法

function reject(self, newValue{
  // reject的时候状态变为2
  self._state = 2;
  // 保存错误信息到_value
  self._value = newValue;
  if (newPromise._onReject) {
    newPromise._onReject(self, newValue);
  }
  finale(self);
}

当Promise执行reject的时候此时状态变为2
保存reject的错误信息到_value调用finale方法

finale方法

function finale(self{
  // 只调用一次then
  if (self._deferredState === 1) {
    handle(self, self._deferreds);
    self._deferreds = null;
  }
  // 调用多次then
  if (self._deferredState === 2) {
    // console.log(self._deferreds);
    for (var i = 0; i < self._deferreds.length; i++) {
      handle(self, self._deferreds[i]);
    }
    self._deferreds = null;
  }
}

finaale方法在这里的作用就相当于handle方法的中转站,根据不同的情况去调用handle方法
上面有提到过_deferredState是用来记录存储的实例状态
「同一个Promise对象下」_deferredState的值是随着then调用的次数决定的,为什么说只有在同一个Promise对象下才会触发呢,我们来看下面一个小例子

const promise2 = new Promise((resolve,reject) => {
    setTimeout(() => {
      resolve('哈哈哈')
    }, 500);
  })
  .then(res => {}) //第一次then
  .then(res => {}) //第二次then

到这里很多小伙伴会以为代码会在’if(self._deferredState === 2){}‘这个判断里面执行
事实上这样去调用then的情况下,_deferredState的值永远只会=1,只会在’if(self._deferredState === 1){}‘的这个判断里面去执行
到这里小伙伴们又会说,不对啊,我这个不是在同一个Promise对象下吗,我不是只实例化了一次吗?
这里在使用Promise的时候确实是只实例化了一次,但是每次调用then方法返回的Promise跟实例的Promise并不是同一个引用,也就是说,这里的self并不是实例出来的对象,后面会详细介绍then是怎么返回Promise对象的

promise2.then(res => {
  console.log(res);
})
promise2.then(res => {
  console.log(res);
})

只有这样调用then,才会执行’if(self._deferredState === 2){}‘这个判断

Promise.prototype.then

Promise.prototype.then = function(onFulfilled, onRejected{
  if (this.constructor !== Promise) {
    return safeThen(this, onFulfilled, onRejected);
  }
  // 新建一个promise实例
  var res = new Promise(noop);
  // // new Handler 构建了一个包含新的promise实例和resolve,reject方法的对象
  handle(thisnew Handler(onFulfilled, onRejected, res));
  // 每次then处理完之后返回一个新的promise实例
  return res;
};

接收两个参数resolvereject函数
一开始先判断实例的构造函数是不是Promise(防止外部修改prototype.constructor)
这里的safeThen函数不作过多解释,主要用于实例化外部实例的构造函数并返回实例
创建一个空的Promise实例给res
我们看看下面这句代码主要做了什么

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

我们拆开来看,先看看new Handler实例化得到了什么

// 这个函数的作用就是每次then的时候通过这个构造函数根据resolve和reject构建一个新的对象
function Handler(onFulfilled, onRejected, promise){
  this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
  this.onRejected = typeof onRejected === 'function' ? onRejected : null;
  this.promise = promise;
}

由此可见通过实例化Handler函数可以得到一个大概长这样子的对象

所以handle函数传入了2个参数,一个是this,一个是像这样子的对象,那么,接下来就可以看handle函数做了什么事情了

function handle(self, deferred{
  // resolve传入的是promise的实例,this(上下文)则改成传入的promise实例
  while (self._state === 3) {
    self = self._value;
  }
  if (newPromise._onHandle) {
    newPromise._onHandle(self);
  }
  // 当padding状态时(没有调用resolve也没有调用reject)
  // 存储新的promise实例
  if (self._state === 0) {
    if (self._deferredState === 0) {
      self._deferredState = 1;
      self._deferreds = deferred;
      return;
    }
    // 已经调用过1次then
    if (self._deferredState === 1) {
      self._deferredState = 2;
      self._deferreds = [self._deferreds, deferred];
      return;
    }
    // 多次then
    self._deferreds.push(deferred);
    return;
  }
  handleResolved(self, deferred);
}

看上面的代码注释大家都应该知道了,handle函数主要就是用作修改_deferredState的值和保存每次then生成的新实例

最后return res 每次调用then方法都返回一个新的Promise实例

链式调用

细心的你可能已经发现了在前面的handle方法里的最下面调用了一个名为handleResolved的函数,话不多说直接上代码

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的回调
  var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
  // 如果then没有回调,则手动调用回调
  if (cb === null) {
    if (self._state === 1) {
      resolve(deferred.promise, self._value);
    } else {
      reject(deferred.promise, self._value);
    }
    return;
  }
  // 获取then的返回值
  var ret = tryCallOne(cb, self._value);
  if (ret === IS_ERROR) {
    reject(deferred.promise, LAST_ERROR);
  } else {
    resolve(deferred.promise, ret);
  }
}

其实源码还引入了asap这个库,我先把这个asap函数注释掉了,理由是懒得用npm,本文全程是html+js+live-server
但是大家千万不要忽略asap这个函数!!!
纵观全文到现在,大家好像并没有发现源码有一点点异步的信息,大家都知道Promise是异步执行的,就是靠asap函数,通过setImmediate这个核心方法去异步执行asap里面的东西,有兴趣的可以去翻翻asap的源码看下具体是怎么实现的
这里只是为了更好的解析源码,没有asap那么Promise就没有意义了,ok我们回归正文

这里就很好理解了
通过tryCallOne函数得到then的返回值
然后再次调用resolve,如果报错或者手动调用reject则调用reject,这样就完成了Promise的链式调用

扩展

src/es6-extensions.js

定义

var TRUE = valuePromise(true);
var FALSE = valuePromise(false);
var NULL = valuePromise(null);
var UNDEFINED = valuePromise(undefined);
var ZERO = valuePromise(0);
var EMPTYSTRING = valuePromise('');

function valuePromise(value{
  var p = new newPromise(newPromise._noop);
  p._state = 1;
  p._value = value;
  return p;
}

Promise.resolve

Promise.resolve = function (value{
  // 判断value是否是Promise得实例
  if (value instanceof Promisereturn value;

  // 因为0,'',null等会被隐式转换成false,所以这里做了精确判断
  if (value === nullreturn NULL;
  if (value === undefinedreturn UNDEFINED;
  if (value === truereturn TRUE;
  if (value === falsereturn FALSE;
  if (value === 0return ZERO;
  if (value === ''return EMPTYSTRING;


  // 这里的判断跟之前resolve方法一样都是判断传入的是否是一个Promise
  if (typeof value === 'object' || typeof value === 'function') {
    try {
      var then = value.then;
      if (typeof then === 'function') {
        return new Promise(then.bind(value));
      }
    } catch (ex) {
      return new Promise(function (resolve, reject{
        reject(ex);
      });
    }
  }
  // 根据valuePromise方法返回一个新的Promise
  return valuePromise(value);
};

try/catch 用于捕获Promise.resolve传入的val是否包含then方法

Promise.all

Promise.all = function (arr{
  var args = iterableToArray(arr);

  return new Promise(function (resolve, reject{
    if (args.length === 0return resolve([]);
    var remaining = args.length;
    function res(i, val{
      if (val && (typeof val === 'object' || typeof val === 'function')) {
        // 如果val是Promise的实例
        if (val instanceof Promise && val.then === Promise.prototype.then) {
          // _state等于3  证明val实例的值也是一个Promise实例,把val替换成新的Promise实例
          while (val._state === 3) {
            val = val._value;
          }
          // resolved成功调用,递归处理resolved的值
          if (val._state === 1return res(i, val._value);
          if (val._state === 2) reject(val._value);
          // 处于padding状态时调用then方法并手动处理值
          val.then(function (val{
            res(i, val);
          }, reject);
          return;
        } else {
          // 如果不是promise的实例且包含then方法
          var then = val.then;
          if (typeof then === 'function') {
            var p = new Promise(then.bind(val));
            p.then(function (val{
              res(i, val);
            }, reject);
            return;
          }
        }
      }
      args[i] = val;
      // promise.all里面全部为fulFilled状态后
      if (--remaining === 0) {
        resolve(args);
      }
    }
    for (var i = 0; i < args.length; i++) {
      res(i, args[i]);
    }
  });
};

第一行用到了iterableToArray函数,这个函数的主要作用是把类数组转换成可被遍历的数组

const p1 = new Promise(() => {})
const p2 = new Promise(() => {})
console.log(Array.isArray(Promise.all[p1,p2]))  //false
// 兼容Array.form这个es6语法
var iterableToArray = function (iterable{
  if (typeof Array.from === 'function') {
    // ES2015+, iterables exist
    iterableToArray = Array.from;
    return Array.from(iterable);
  }

  // ES5, only arrays and array-likes exist
  iterableToArray = function (xreturn Array.prototype.slice.call(x); };
  return Array.prototype.slice.call(iterable);
}

使用Array.prototype.slice.call(x)对Array.from做了兼容

Promise.all = function (arr{
  var args = iterableToArray(arr);

  return new Promise(function (resolve, reject{
    if (args.length === 0return resolve([]);
    var remaining = args.length;
    function res(i, val{
      ...
    }
    for (var i = 0; i < args.length; i++) {
      res(i, args[i]);
    }
  });
};

先不要看res方法,先看看怎么处理传进来的参数
循环调用res处理传进来的每一项,第一个参数为下标,第二个参数是数组的每一项
再来看下res方法

function res(i, val{
      if (val && (typeof val === 'object' || typeof val === 'function')) {
        // 如果val是Promise的实例
        if (val instanceof Promise && val.then === Promise.prototype.then) {
          // _state等于3  证明val实例的值也是一个Promise实例,把val替换成新的Promise实例
          while (val._state === 3) {
            val = val._value;
          }
          // resolved成功调用,递归处理resolved的值
          if (val._state === 1return res(i, val._value);
          if (val._state === 2) reject(val._value);
          // 处于padding状态时调用then方法并手动处理值
          val.then(function (val{
            res(i, val);
          }, reject);
          return;
        } else {
          // 如果不是promise的实例且包含then方法
          var then = val.then;
          if (typeof then === 'function') {
            var p = new Promise(then.bind(val));
            p.then(function (val{
              res(i, val);
            }, reject);
            return;
          }
        }
      }
      args[i] = val;
      // promise.all里面全部为fulFilled状态后
      if (--remaining === 0) {
        resolve(args);
      }
    }

如果传进来的val(args的每一项)不是对象或者function的话,那么直接视为结果值把args[i]给替换掉

args[i] = val;

如果传进来的是一个Promise,则

if (val instanceof Promise && val.then === Promise.prototype.then) {
      ...
}

如果传进来的val不是Promise且包含then方法,则

else {
  // 如果不是promise的实例且包含then方法
  var then = val.then;
  if (typeof then === 'function') {
    var p = new newPromise(then.bind(val));
    p.then(function (val{
      res(i, val);
    }, reject);
    return;
  }
}

重点在这一段

// _state等于3  证明val实例的值也是一个Promise实例,把val替换成新的Promise实例
  while (val._state === 3) {
    val = val._value;
  }
  // resolved成功调用,递归处理resolved的值
  if (val._state === 1return res(i, val._value);
  if (val._state === 2) reject(val._value);
  // 处于padding状态时调用then方法并手动处理值
  val.then(function (val{
    res(i, val);
  }, reject);
  return;

只有当Promise的状态为Fulfilled的时候,实例的value才会被正确的处理,否则会执行return,所以只要有一个Promise未能成功Fulfilled都不会执行resolve(args)

//满足不了条件
if (--remaining === 0) {
    resolve(args);
}

当得到所有结果值的时候(args[i] = val) == args[i] = val.value,调用resolve方法并传入结果数组args

看个例子

const promise2 = new Promise((resolve,reject) => {
    setTimeout(() => {
      resolve('哈哈哈')
    }, 700);
})
const promise3 = new Promise((resolve,reject) => {
    setTimeout(() => {
      resolve('哈哈哈2')
    }, 600);
})
  
newPromise.all([promise2,promise3])
.then(res => {
    console.log(res);  //['哈哈哈','哈哈哈2']
})

这里无论什么时候resolve,最后得出的结果数组res都是按照Promise.all([])数组里面的顺序来输出的,这也印证了源码中为什么要把下标传入到res()的原因(res(i, args[i]))

Promise.race

Promise.race = function (values{
  return new Promise(function (resolve, reject{
    iterableToArray(values).forEach(function(value){
      Promise.resolve(value).then(resolve, reject);
    });
  });
};

Promise.race传入一个数组,用Promise.resolve处理每一项并调用then方法
最后返回一个Promise实例
这里的Promise.resolve(value).then(resolve, reject)当传入的Promise谁的状态首先变为Fulfilled,谁就先调用then
因为Promise执行resolve一次之后状态就不会改变,所以race传入多个Promise,谁的状态先变为Fulfilled,race就返回哪个

const promise2 = new Promise((resolve,reject) => {
    setTimeout(() => {
      resolve('哈哈哈')
    }, 700);
})
const promise3 = new Promise((resolve,reject) => {
    setTimeout(() => {
      resolve('哈哈哈2')
    }, 600);
})

Promise.race([promise2,promise3])
.then(res => {
    console.log(res);  //哈哈哈2
})



如果你觉得这篇内容对你挺有启发,我想邀请你帮我三个小忙:

  1. 点个「在看」,让更多的人也能看到这篇内容(喜欢不点在看,都是耍流氓 -_-)
  2. 欢迎加我微信「TH0000666」一起交流学习...
  3. 关注公众号「前端Sharing」,持续为你推送精选好文。


浏览 14
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报