Promise 对象,相信学前端的小伙伴们都对它很熟了,它是一种现在前端异步编程的主流形式,关于它的文章从基础使用到底层原理分析、实现网上是一抓一大把,它也是现在前端面试的高频题目,深度掌握它也成为一个衡量前端能力的标准了。

之前也写过一篇关于 Promise 对象的文章(一文,一定能让你手动实现Promise),不过那会写得比较简单,这次算是来给它补全(来还债了(ー_ー)!!),废话不多说,下面马上就来开始本文的愉快之旅。

这次所写的 Promise 对象是以 Promises/A+ (中文规范)为标准的,ES6 中就是采用了 Promise/A+ 规范。

Promise 的规范有很多,如 Promise/APromise/BPromise/D 以及 Promise/A 的升级版 Promise/A+Promise/A+ 并未规范 catchallrace 等这些方法,这些方法都是 ES6 自己规范的,其实也不难,后面也会一一讲解到。

源码实现

本章尝试以例子的形式来写出整个 Promise 对象的源码,每行代码会备注详细的注解不怕看不懂哦。

基础结构

  • 我们以下面基本使用的例子先来引出 Promise 对象的整体结构。
let p = new Promise((resolve, reject) => {
  console.log('Start')
  resolve('橙某人');
})
p.then(r1 => {
  console.log(r1)
}, e1 => {
  console.log(e1)  
})
console.log('End');
复制代码

image.png

通过上面 Promise 对象的基本使用,我们能先定义出它的构造函数、.then(onFulfilled, onRejected) 方法以及还有两个回调函数 resolve()reject() 的定义,当然,它们是形参,名字你可以随便定义。

// 定义构造函数
function myPromise(executor) {
  let _this = this; // 保留 this 的指向,防止下面使用过程中 this 指向不明
  _this.status = 'pending'; // status有三种状态: pending || fulfilled || rejected
  _this.result = undefined; // 保存调用 resolve(result) 方法传递的值
  _this.reason = undefined; // 保存调用 reject(reason) 方法传递的值
  
  // 定义 resolve() 
  function resolve(result) {
    if(_this.status === 'pending') {
      _this.result = result; // 保存成功的值
      _this.status = 'fulfilled'; // 把 Promise 对象的状态改为成功
    }
  }
  // 定义 reject()
  function reject(reason) {
    if(_this.status === 'pending') {
      _this.status = 'rejected'; // 保存失败的值
      _this.reason = reason; // 把 Promise 对象的状态改为失败
    }
  }
  // 立即执行 executor(), 执行 executor() 过程可能会出错, 所以要严谨进行try/catch一下, 并且错误结果能给 Promise 捕获
  try{
    executor(resolve, reject);
  }catch(err) {
    reject(err)
  }
}
// 定义 .then() 方法, 它的参数是可选的, 只要不是函数就忽略即可
myPromise.prototype.then = function (onFulfilled, onRejected) {
  if(this.status === 'fulfilled' && typeof onFulfilled === 'function') {
    // .then() 调用是一个异步过程
    setTimeout(() => {
      onFulfilled(this.result);
    })
  }
  if(this.status === 'rejected' && typeof onRejected === 'function') {
    setTimeout(() => {
      onRejected(this.reason);
    })
  }
}
复制代码

一个 Promise 会有三种状态,分别是 pending/fulfilled/rejected ,可以简单理解为等待中、成功和失败三种状态,这是基本知识了,就不作过多介绍了。整体过程不是很难,唯一需要注意的是 .then() 方法是一个异步方法,所以我们把它们放在 setTimeout 中去执行。

  • 下面我们来调整一下上面的例子:
let p = new Promise((resolve, reject) => {
  console.log('Start')
  setTimeout(() => {
    resolve('橙某人');
  })
})
p.then(r1 => {
  console.log(r1)
}, e1 => {
  console.log(e1)  
})
console.log('End');
复制代码

我们异步调用了 resolve('橙某人'); 但它的运行结果和上面的一致,来看看如何实现这个异步调用过程。

function myPromise(executor) {
  ...
  _this.onFulfilledCbs = []; // 调用 then(onFulfilled, onRejected) 方法时, 如果 status 为 pending 状态, 说明 resolve() 为异步调用, 所以 Promise 状态还没改变, 则先将 onFulfilled 回调函数存储起来
  _this.onRejectedCbs = []; // 调用 then(onFulfilled, onRejected) 方法时, 如果 status 为 pending 状态, 说明 reject() 为异步调用, 所以 Promise 状态还没改变, 则先将 onRejected 回调函数存储起来
  
  function resolve(result) {
    if(_this.status === 'pending') {
      _this.result = result;
      _this.status = 'fulfilled';
      // 执行所有 then(onFulfilled, onRejected) 方法的 onFulfilled
      while (_this.onFulfilledCbs.length) {
        _this.onFulfilledCbs.shift()(); // 按 then() 方法的调用顺序执行并删除
      }
    }
  }
  function reject(reason) {
    if(_this.status === 'pending') {
      _this.reason = reason;
      _this.status = 'rejected';
      // 执行所有 then(onFulfilled, onRejected) 方法的 onRejected
      while (_this.onRejectedCbs.length) {
        _this.onRejectedCbs.shift()(); // 按 then() 方法的调用顺序执行并删除
      }
    }
  }
  ...
}
myPromise.prototype.then = function (onFulfilled, onRejected) {
  ...
  // Promise 对象还是 pending 状态, 则说明 resolve() 或者 reject() 进行了异步调用
  if(this.status === 'pending') {
    if(typeof onFulfilled === 'function') {
      /**
       * 之所以 push 一个函数, 主要是为了回调函数能传值.
       * 也可以直接 push 函数, 传值交由上面来完成: 
       * this.onFulfilledCbs.push(onFulfilled);
       * _this.onFulfilledCbs.shift()(_this.result)
       */
      this.onFulfilledCbs.push(() => {
        onFulfilled(this.result);
      })  
    }
    if(typeof onRejected === 'function') {
      this.onRejectedCbs.push(() => {
        onRejected(this.reason);
      })  
    }  
  }
}
复制代码

其实异步调用 resolve() 或者 reject().then() 方法会比它们俩先执行,那么 .then() 内部使用的 Promise 对象的状态会是 pending 等待状态,那么我们只能先把 .then(onFulfilled, onRejected) 方法的成功回调和失败回调先存起来,只有当真正调用过 resolve() 或者 reject() 再去把它们拿出来执行即可。

then() 方法

Promise 对象的基本结构不是很难,也比较好理解,它最强大的地方是它支持链式调用,这是最复杂也是最重点的内容,接下来我们来研究研究它的链式调用,上重头戏了。

链式调用

我们一样先看例子:

let p = new Promise((resolve, reject) => {
  console.log('Start')
  setTimeout(() => {
    resolve('橙某人');
  })
})
p.then(r1 => {
  return r1;
}).then(r2 => {
  return r2;
}).then(r3 => {
  console.log(r3); // 橙某人
})
console.log('End');
复制代码

由例子上看,我们可以知道每次调用完 then() 方法必然会返回一个 Promise 对象,它才能接下来继续链式调用下去。而且, then() 中能使用 return 把结果继续往后传递下去,基于这两点我们再来改造下 then() 方法。

myPromise.prototype.then = function (onFulfilled, onRejected) {
  // 返回一个新的 Promise 对象
  return new myPromise((resolve, reject) => {
    if(this.status === 'fulfilled' && typeof onFulfilled === 'function') {
      setTimeout(() => {
        // 执行 onFulfilled or onRejected 方法可能会出现错误, 所以try/catch一下,并把错误往下传递
        try{
          // 获取到 return 的值
          let x = onFulfilled(this.result);
          // 调用新的 Promise 对象的 resolve() 把值继续传下去
          resolve(x);
        }catch(e) {
          reject(e);
        }
      })
    }
    if(this.status === 'rejected' && typeof onRejected === 'function') {
      setTimeout(() => {
        try{  
          let x = onRejected(this.reason);
          reject(x);
        }catch(e) {
          reject(e);
        }
      })
    }
    if(this.status === 'pending') {
      if(typeof onFulfilled === 'function') {
        this.onFulfilledCbs.push(() => {
          try{  
            let x = onFulfilled(this.result);
            resolve(x);
          }catch(e) {
            reject(e);
          }
        })  
      }
      if(typeof onRejected === 'function') {
        this.onRejectedCbs.push(() => {
          try{ 
            let x = onRejected(this.reason);
            reject(x);
          }catch(e) {
            reject(e);
          }
        })  
      }  
    }
  })
}
复制代码

通过在 then() 中返回一个新的 Promise 对象,就能实现无限链式调用下去了,是不是挺简单;而通过执行 onFulfilled() 或者 onRejected() 方法来获取return 的值,也就是上面代码中的 x ;最后我们通过新的 Promise 对象的 resolve(x)reject(x) 方法就能把值继续往下传啦(-^〇^-)。当然执行 onFulfilled() 或者 onRejected() 可能会出现错误,所以我们都加上了 try/catch 来捕获一下错误。

值穿透

然后,你以为这就完了吗?还没呢,我们再来看个例子:

let p = new myPromise((resolve, reject) => {
  console.log('Start')
  setTimeout(() => {
    resolve('橙某人');
  })
})
p
.then()
.then()
.then()
.then(r4 => {
  console.log(r4); // 橙某人
})
console.log('End');
复制代码

是不是这种“值穿透”传递也挺神奇的?我们来看看它要如何去实现。

myPromise.prototype.then = function (onFulfilled, onRejected) {
  // 解决值穿透
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : result => result;
  onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason}; // onRejected 直接用 throw 抛出错误,这样它会被 try/catch 捕获到; 注意 onRejected 中的错误不能以 return 的形式传递,都是通过 throw 往后抛下去
  // 返回一个新的 Promise 对象
  ...
}
复制代码

其实这个过程就是一个初始化默认值过程,也就是 .then() 会变成 .then(result => result),前面我们说过对于 then() 方法的两个参数 onFulfilledonRejected 我们只考虑它们是函数的情况。现在,当它们不是函数的时候,我们就给它们默认值一个函数并且把结果继续往后抛,就能实现所谓的值穿透了。

值类型判断

接下来就是 then() 方法的最后一步了,这一步也挺复杂的,需要对 then(onFulfilled, onRejected) 两个参数 return 的值进行验证,也就是对 x 做一些类型检验。按照 Promises/A+ 标准规范的信息,我们需要对 x 做这以下几个校验:

image.png

53c9a8f7fa6821ab08162a7e7d309dd.jpg

呃......是不是都看懵逼了,这是图灵社区译的中文文档,应该算是很贴近了,你也可以去看看英语文档,其实应该也差不多,一般官方文档的话语都是那么难以理解的。不过,不用着急,我把测试例子写出来,你可能就好理解很多了。

  1. x 与 then() 新返回的 Promise 相等。
let p = new Promise((resolve, reject) => {
  console.log('Start')
  resolve('橙某人');
})
let t = p.then(() => {
  return t
})
console.log('End');
复制代码

image.png

  1. x 为另一个 Promise 对象。

新的 Promise 对象状态处于 fulfilled 或者 rejected 状态。

let p = new Promise((resolve, reject) => {
  console.log('Start')
  resolve('橙某人');
});
let pNew = new Promise((resolve, reject) => {
  resolve('yd');
  // reject('yd');
});
p.then(() => {
  return pNew
}).then(r => {
  console.log(r);
});
console.log('End');
复制代码

image.png

新的 Promise 对象状态处于 pending 状态。

let p = new Promise((resolve, reject) => {
  console.log('Start')
  resolve('橙某人');
})
let pNew = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('yd'); // 1秒后才调用, 让 Promise 处于 pending 状态
  }, 1000)
});
p.then(() => {
  return pNew
}).then(r => {
  console.log(r); // 一秒后才输出
});
console.log('End');
复制代码

image.png

第二个测试的两个小例子,结果虽然一样,但第一个的结果是控制台立即就输出了 yd,但第二个的结果是控制台等了一秒后才输出 yd

  1. 如果 x 为对象或者函数。
let p = new Promise((resolve, reject) => {
  console.log('Start')
  resolve('橙某人');
})
// x 为对象
p.then(() => {
  return {
    then: function(resolve, reject) {
      console.log('我是对象的then属性', this);
      resolve('yd')
    }
  }
}).then(r => {
  console.log(r); // yd
}, e => {
  console.log(e)
})
// x 为函数
function fn() {}
fn.then = function(resolve, reject) {
  console.log('我是对函数的then属性', this);
  resolve('yd')
}
p.then(() => {
  return fn;
}).then(r => {
  console.log(r); // yd
})

console.log('End');  
复制代码

image.png

这样子是不是有点像是把对象和函数的 then 属性造了一个新的 Promise 对象呢?这也告诉我们取名字的时候要注意哦,千万不要和关键字有关联(^ω^)。

下面我们先来看看 then() 方法的调整:

myPromise.prototype.then = function (onFulfilled, onRejected) {
  ...
  var p = new myPromise((resolve, reject) => {
    if(this.status === 'fulfilled' && typeof onFulfilled === 'function') {
      setTimeout(() => {
        let x = onFulfilled(this.result);
        // 借助 resolvePromise() 统一验证 x 
        resolvePromise(p, x, resolve, reject);
      })
    }
    if(this.status === 'rejected' && typeof onRejected === 'function') {
      setTimeout(() => {
        let x = onRejected(this.reason);
        resolvePromise(p, x, resolve, reject);
      })
    }
    if(this.status === 'pending') {
      if(typeof onFulfilled === 'function') {
        this.onFulfilledCbs.push(() => {
          let x = onFulfilled(this.result);
          resolvePromise(p, x, resolve, reject);
        })  
      }
      if(typeof onRejected === 'function') {
        this.onRejectedCbs.push(() => {
          let x = onRejected(this.reason);
          resolvePromise(p, x, resolve, reject);
        })  
      }  
    }
  })
  return p;
}
复制代码

因为对 x 验证的部分比较复杂和重复,所以另外写一个 resolvePromise() 方法。

/**
 * 验证 x 的类型
 * @param {*} p: 新返回的 Promise 对象
 * @param {*} x: onFulfilled or onRejected 返回的值
 * @param {*} resolve: 新 Promise 对象的 resolve 方法
 * @param {*} reject: 新 Promise 对象的 reject 方法
 * @returns 
 */
function resolvePromise(p, x, resolve, reject) {
  // 1. 如果 x 为新返回的 Promise 对象, 则直接抛出错误
  if (x != undefined && p === x) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
  }

  // 2. 如果 x 为另一个 myPromise 对象, 也就是显式 return new myPromise() 
  if (x instanceof myPromise) {
    if (x.status === 'pending') { // 如果 x 这个 myPromise 对象处于  pending 状态, 则要等待直 x 被执行或拒绝
      x.then(r => {
        resolvePromise(p, r, resolve, reject); // 递归等待 x 被执行
      }, e => {
        reject(e);
      })
    } else { // 如果 x 这个 myPromise 对象已经处于  fulfilled or rejected 状态, 则直接再次调用 then() 方法!
      x.then(resolve, reject); // 这里的意思其实可以理解为, 我们执行了新的 myPromise 对象的 then() 方法, 它的两个回调参数, 就是我们下一个 then() 两个回调的参数. 
    }
    return;
  }

  // 3. 如果 x 为对象或者函数, 如果不是就是普通的数据类型, 直接通过
  var called; // 只调用一次, 防止重复调用
  if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
    // 如果 x.then 为一个函数, 则我们会执行这个函数, 并且函数内部的 this 指向 x, 执行这个函数内部可能会有错误, 所以需要 try/catch 一下
    try {
      let then = x.then; // 获取 x 身上的 then() 方法
      if (typeof then === 'function') {
        then.call(x, function (res) {
          if (called) return;
          called = true;
          // 可能 res 还是一个 promise 对象, 所以需要递归下去判断 res(也就是x) 的类型
          resolvePromise(p, res, resolve, reject); // 递归
        }, function (err) {
          if (called) return;
          called = true;
          reject(err)
        })
      } else {
        resolve(x); // 普通值, x.then 可能是 x.then = '橙某人
      }
    } catch (e) {
      reject(e)
    }
  } else {
    resolve(x); // 普通值
    // 这里为什么直接就调用 resolve() 不考虑 reject(), 因为规定错误不能通过 return 来传递, 只能通过 throw 的形式, 也就是 onRejected 中 return 是无效的
  }
}
复制代码

resolvePromise() 方法是比较复杂的,从上面文字的介绍你就应该能感觉到,它里面涉及到了递归的概念,我觉得看懂它的最好方式就是跟着例子慢慢去分析,加油骚年,有什么疑问也欢迎你留言给我,必回。