前言
如果我们想要深入地学习 JavaScript,那么手写 Promise 无可避免。
手写 Promise 需要使用多种技术手段和逻辑思路,对学习 JavaScript 或者其他语言、框架等都可以起到积极的作用。
本篇文章详细记录笔者尝试实现 Promise 的每个步骤以及在此过程中遇到的各种问题。
Promises/A+ 规范
Promise 并不是一个新事物,而是按照一个规范实现的类。这个规范有多个版本,如 Promises/A、Promises/B、Promises/D 以及 Promises/A+ 等。ES6 采用 Promises/A+ 这一版,那么我们自然也就按照这一版来实现 Promise。
Promises/A+ 规范包含术语(Terminology)、要求(Requirements)和注意事项(Notes)3 个部分。我们主要按第二部分逐步实现 Promise。
实现步骤
I. Promise States
一个合乎规范的 promise 应该有 pending、fulfilled、rejected 三个状态;
 
promise 的状态可以由 pending 转为 fulfilled 或者 rejected,这个转换过程不可逆;
 
fulfilled 和 rejected 状态不可再转变为其他状态;
 
当 promise 状态由 pending 转变为 fulfilled 时,必须有一个 value 且不可改变;
 
当 promise 状态由 pending 转变为 rejected 时,必须有一个 reason 且不可改变;
 
value 和 reason 的 “不可改变”是指 指向不变,非 deep 层级的不可改变。
Here, “must not change” means immutable identity (i.e. ===), but does not imply deep immutability.
 
代码实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
   | // 3个状态 const stateEnum = {   PENDING: 'PENDING',   FULFILLED: 'FULFILLED',   REJECTED: 'REJECTED', };
  class MyPromise {   constructor(executor) {     this.state = stateEnum.PENDING; // 当前状态,初始为 pending     this.value = null; // 状态转变为 fulfilled 时的 value     this.reason = null; // 状态转变为 rejected 时的 reason
      // 更改状态;因 resolve/reject 在 executor 中执行,使用箭头函数固定 this 指向     const resolve = (value) => {       // pending => fulfilled 单向不可逆       if (this.state === stateEnum.PENDING) {         this.state = stateEnum.FULFILLED;         this.value = value;       }     };     const reject = (reason) => {       // pending => rejected 单向不可逆       if (this.state === stateEnum.PENDING) {         this.state = stateEnum.REJECTED;         this.reason = reason;       }     };
      // 立即执行     try {       // 传入方法 resolve, reject       executor(resolve, reject);     } catch (err) {       // 执行异常时,失败回调       reject(err);     }   } }
  const promise = new MyPromise();
   | 
 
II. The then Method
一个 promise 应该提供一个 then 方法,用于获取这个 promise 当前/最终的 value 或 reason;
 
then 方法接收两个可选参数 onFulfilled 和 onRejected:
1
   | promise.then(onFulfilled, onRejected)
   | 
 
参数 onFulfilled 和 onRejected 均为可执行函数;
 
promise 在 fulfilled 之后调用函数 onFulfilled ,参数为 promise 的 value;
 
promise 在 rejected 之后调用函数 onRejected ,参数为 promise 的 reason;
1 2 3 4 5 6 7 8 9 10 11 12
   | ... class MyPromise {   ...   then(onFulFilled, onRejected) {     // onFulFilled / onRejected 必须是函数     typeof onFulFilled === 'function' ? onFulFilled : (v) => v;     typeof onRejected === 'function' ? onRejected : (err) => { throw err };
      if (this.state === stateEnum.FULFILLED) onFulFilled(this.value);     if (this.state === stateEnum.REJECTED) onRejected(this.reason);   } }
   | 
 
只有当执行栈仅包含平台代码时,onFulfilled 和 onRejected 才可以执行 ;
- 用以确保 
onFulfilled 和 onRejected 可以异步地在新的执行栈中执行; 
- 可以通过宏任务(如 
setTimeout)、微任务(如 process.nextTick)来实现; 
 
then 可以被同一个 promise 调用多次;
- 当 promise 状态发生改变时,
onFulfilled 和 onRejected 必须按其注册顺序依次执行; 
 
then 函数必须返回一个 promise;
1
   | promise2 = promise1.then(onFulfilled, onRejected);
   | 
 
- 如果 
onFulfilled 或 onRejected 返回一个值(假定为 x ),调用 Promise 解析程序([[Resolve]](promise2, x)); 
- 如果 
onFulfilled 或 onRejected 抛出一个异常(假定为 e ),promise2 必须以 e 为 reason 值转变为 rejected 状态; 
- 如果 
onFulfilled 不是函数,且 promise1 已经处于 fulfilled 状态,promise2 必须以和 promise1 相同的 value 转变为 fulfilled 状态; 
- 如果 
onRejected 不是函数,且 promise1 已经处于 rejected 状态,promise2 必须以和 promise1 相同的 reason 转变为 rejected 状态; 
 
代码实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
   | // 3个状态 const stateEnum = {   PENDING: 'PENDING',   FULFILLED: 'FULFILLED',   REJECTED: 'REJECTED', };
  class MyPromise {   // 构造函数,参数为可执行函数   constructor(executor) {     this.state = stateEnum.PENDING; // 当前状态,初始为 pending     this.value = null; // 状态转变为 fulfilled 时的 value     this.reason = null; // 状态转变为 rejected 时的 reason
      // 更改状态;因 _resolve/_reject 在 executor 中执行,使用箭头函数固定 this 指向     const _resolve = (value) => {       // pending => fulfilled 单向不可逆       if (this.state === stateEnum.PENDING) {         // 状态变化         this.state = stateEnum.FULFILLED;         this.value = value;       }     };     const _reject = (reason) => {       // pending => rejected 单向不可逆       if (this.state === stateEnum.PENDING) {         // 状态变化         this.state = stateEnum.REJECTED;         this.reason = reason;       }     };
      try {       // 立即执行 executor 并传入实参 _resolve, _reject       executor(_resolve, _reject);     } catch (err) {       // 执行异常时,失败回调       _reject(err);     }   }
    then(onFulFilled, onRejected) {     // onFulFilled / onRejected 必须是函数     typeof onFulFilled === 'function' ? onFulFilled : (v) => v;     typeof onRejected === 'function' ? onRejected : (err) => { throw err };
      if (this.state === stateEnum.FULFILLED) onFulFilled(this.value);     if (this.state === stateEnum.REJECTED) onRejected(this.reason);   } }
  const promise = new MyPromise();
   | 
 
至此,我们已经实现了一个简单的 Promise 。简单验证如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
   | const result = 'resolve'; const promise = new MyPromise((resolve, reject) => {   if (result === 'fulfilled') {     resolve('fulfilled');   } else {     reject('rejected');   } }); promise.then(   (res) => {     console.log('fulfilled value:', res);   },   (err) => {     console.log('rejected reason:', err);   } );
   | 
 
程序正常,没有问题。
但是,参考 Promises/A+ 标准和正常 Promise 的使用方式,这个 Promise 还是存在许多问题:
上述 6、7、8 条标准未实现;
 
由于 executor 中可能存在异步操作,以至于 then 执行时,promise 可能仍然处于 pending 状态,也就是说,onFulfilled 和 onRejected 的执行时机有问题;
 
按上述规则 No.6(Promises/A+ 2.2.4)的要求,onFulfilled 和 onRejected 需要异步调用,这里是同步。
Promises/A+ 3.1 “In practice, this requirement ensures that onFulfilled and onRejected execute asynchronously”
 
我们姑且先解决问题 2 和 3,规则 No.6 和 No.7。至于规则 No.8,等我们阅读完 III. The Promise Resolution Procedure 这一部分再来解决。
A. 解决异步 executor 的问题
来捋一捋思路:
- 在我们 
promise.then() 时,then 就已经执行,我们能做的就是将延迟执行 then 的两个函数参数 onFulfilled 和 onRejected ,而不是延迟 then ; 
resolve 和 reject 在 executor 内部执行,按正常的 Promise 的用法,就是在异步函数的最后执行。那么,不妨将 onFulfilled 和 onRejected 放到 resolve / reject 中执行以达到延迟执行的目的; 
代码实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
   | class MyPromise {   // 构造函数,参数为可执行函数   constructor(executor) {     this.state = stateEnum.PENDING; // 当前状态,初始为 pending     this.value = null; // 状态转变为 fulfilled 时的 value     this.reason = null; // 状态转变为 rejected 时的 reason     this.onFulfilled = v => v; // 存储,以延迟执行     this.onRejected = v => v; // 存储,以延迟执行
      // 更改状态;因 _resolve/_reject 在 executor 中执行,使用箭头函数固定 this 指向     const _resolve = (value) => {       // pending => fulfilled 单向不可逆       if (this.state === stateEnum.PENDING) {         // 状态变化         this.state = stateEnum.FULFILLED;         this.value = value;         // 状态改变时执行         this.onFulfilled();       }     };     const _reject = (reason) => {       // pending => rejected 单向不可逆       if (this.state === stateEnum.PENDING) {         // 状态变化         this.state = stateEnum.REJECTED;         this.reason = reason;         // 状态改变时执行         this.onRejected();       }     };
      try {       // 立即执行 executor 并传入实参 _resolve, _reject       executor(_resolve, _reject);     } catch (err) {       // 执行异常时,失败回调       _reject(err);     }   }
    then(onFulfilled, onRejected) {     // onFulfilled / onRejected 必须是函数     typeof onFulfilled === 'function' ? onFulfilled : (v) => v;     typeof onRejected === 'function' ? onRejected : (err) => { throw err };
      switch (this.state) {       case stateEnum.FULFILLED:         onFulfilled(this.value);         break;       case stateEnum.REJECTED:         onRejected(this.reason);         break;       case stateEnum.PENDING:         // 当状态为 pending 时,存储而不执行         // 使用箭头函数嵌套,确定 this 指向         this.onFulfilled = () => {           onFulfilled(this.value);         }         this.onRejected = () => {           onRejected(this.value);         }         break;     }   } }
  | 
 
我们给 MyPromise 类新增了两个属性 onFulfilled 和 onFulfilled,在 then 执行时,若 promise 的状态仍为 pending,那么把 then 的两个参数存储起来(赋值给 this.onFulfilled 和 this.onRejected),在状态变化(_resolve 或 _reject 执行时)时调用。如此,便可以实现延迟调用、异步执行了。
B. 规则 No.6:onFulfilled 和 onRejected 异步调用
根据 Promises/A+ 规则 3.1 的描述,我们使用 setTimeout 来保证 onFulfilled 和 onRejected 的异步调用。
代码实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
   | class MyPromise {   ...   then(onFulfilled, onRejected) {     ...     switch (this.state) {       ...       case stateEnum.PENDING:         this.onFulfilled = () => {           // setTimeout 实现异步执行           setTimeout(() => {             onFulFilled(this.value);           }, 0);         }         this.onRejected = () => {           // setTimeout 实现异步执行           setTimeout(() => {             onFulFilled(this.value);           }, 0);         }         break;     }   } }
  | 
 
C. 规则 No.7:then 的多次调用
按上述规则 No.7(Promises/A+ 2.2.6),同一个 promise 可以多次调用 then,也就是说会有这种情况:
1 2 3 4 5 6 7 8
   | promise.then(   res => { console.log('res-1') },   err => { console.log('err-1') } ); promise.then(   res => { console.log('res-2') },   err => { console.log('err-2') } );
   | 
 
我们目前实现的 Promise ,由于 this.onFulfilled 和 this.onRejected 都是直接赋值的,就导致上面这种用法会有覆盖的风险。
既然直接赋值的方式不行,不妨使用数组存储。需要时取出执行,之后去除数组元素即可。
优化代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
   | class MyPromise {   // 构造函数,参数为可执行函数   constructor(executor) {     this.state = stateEnum.PENDING; // 当前状态,初始为 pending     this.value = null; // 状态转变为 fulfilled 时的 value     this.reason = null; // 状态转变为 rejected 时的 reason     this.onFulFilledQueue = []; // fulfilled 时调用的函数执行队列,存储 then 的 onFulFilled 函数     this.onRejectedQueue = []; // rejected 时调用的函数执行队列,存储 then 的 onRejected 函数
      // 更改状态;因 _resolve/_reject 在 executor 中执行,使用箭头函数固定 this 指向     const _resolve = (value) => {       // pending => fulfilled 单向不可逆       if (this.state === stateEnum.PENDING) {         // 状态变化         this.state = stateEnum.FULFILLED;         this.value = value;         // 状态改变时执行         while (this.onFulFilledQueue.length) {           const cb = this.onFulFilledQueue.shift();           cb && cb();         }       }     };     const _reject = (reason) => {       // pending => rejected 单向不可逆       if (this.state === stateEnum.PENDING) {         // 状态变化         this.state = stateEnum.REJECTED;         this.reason = reason;         // 状态改变时执行         while (this.onRejectedQueue.length) {           const cb = this.onRejectedQueue.shift();           cb && cb();         }       }     }; 	...   }
    then(onFulFilled, onRejected) {     ...     switch (this.state) {       ...       case stateEnum.PENDING:         // 当状态为 pending 时,存储而不执行         // 使用箭头函数嵌套,确定 this 指向         this.onFulFilledQueue.push(() => {           // setTimeout 实现异步执行           setTimeout(() => {             onFulFilled(this.value);           }, 0);         });         this.onRejectedQueue.push(() => {           // setTimeout 实现异步执行           setTimeout(() => {             onRejected(this.reason);           });         });         break;     }   } }
  | 
 
很明显,我们采用了发布订阅模式来实现延迟执行。
III. The Promise Resolution Procedure
The Promise Resolution Procedure,姑且称之为 Promise 解析程序,函数表示为 [[Resolve]](promise, x)
代码实现如下
IV. then 链式调用与值穿透
当 then 函数 return 了一个值,我们总能在下一个 then(onFulfilled)中取到,这就是 then 的链式调用;
而如果前一个 then 函数没有传入任何参数(promise.then().then()),则可以在后面的 then 中获取之前 then 返回的值,此即 then 的值穿透。
小结
Promises/A+
 
Promise 知识汇总和面试情况
 
面试官:“你能手写一个 Promise 吗”