手撕 promise A+

174 阅读11分钟

本文中 promise 实现严格遵循 A+ 规范,并通过官方全部用例测试,详见promise A+ 官方文档

本文尽量由浅入深,从具体场景去剖析源码细节,如果感觉实在晦涩难懂,可以先移步至promise 基础学习基础用法。

术语直译

  1. “promise” is an object or function with a then method whose behavior conforms to this specification. 「promise 是一个其行为符合规范的对象或函数
  2. “thenable” is an object or function that defines a then method. 「thenable 是一个拥有 then 方法的对象或函数
  3. “value” is any legal JavaScript value (including undefined, a thenable, or a promise). 「resolve 或者 reject 的参数 value 是任何合法的 JavaScript 值(包括 undefined、thenable 或 promise)
  4. “exception” is a value that is thrown using the throw statement. 「异常是使用 throw 语句抛出的值
  5. “reason” is a value that indicates why a promise was rejected. 「reason 是一个值,表示 promise 被拒绝的原因

基础用法介绍

// 前置基础知识
//   @1 Promise 构造函数接收一个 executor 函数作为参数,它会立即执行
//   @2 Promise 有三个状态,pending「等待态」、fulfilled「成功态」、reject「失败态」
//   @3 只有在 pending 的状态时才能改变状态,不能从成功到失败,也不能从失败到成功
//   @4 throw error 抛出异常也会执行失败的逻辑
var promise = new Promise((resolve, reject) => {
  throw new Error('onError');

  resolve('ok');
  // reject('不ok');
});

promise.then(val => {
  console.log(val, 'success');
}, reason => {
  console.log(reason, 'fail');
});

// onError, 'fail'

那么,我们由此来写一个简版的 promise 吧~

乞丐版 promise

// 细节:敲黑板
//   @1 因为每一个 promise 有自己的 resolve 和 reject 方法,所以两个方法没有放到原型上
//   @2 resolve 和 reject 使用箭头函数,所以不用修正内部 this
//   @3 then 中要用到成功或失败的入参,所以把 value 和 reason 挂在 this 上
//   @4 executor 函数如果执行抛出异常,按照 reject(error) 来处理
//   @5 promise 实例的状态只能从 pending 被改变一次
const PENDING = 'PENDING'; // 等待态「默认状态」
const FULFILLED = 'FULFILLED'; // 成功态
const REJECTED = 'REJECTED'; // 失败态

class _Promise {
  constructor(executor) {
    this.status = PENDING;
    this.value = undefined;
    this.reason = undefined;

    const resolve = (value) => {
      if (this.status !== PENDING) return;
      this.value = value;
      this.status = FULFILLED;
    }

    const reject = (reason) => {
      if (this.status !== PENDING) return;
      this.reason = reason;
      this.status = REJECTED;
    }

    try {
      executor(resolve, reject);
    } catch(e) {
      reject(e);
    }
  }

  // 原型上的 then 方法
  then(onFulfilled, onRejected) {
    if (this.status == FULFILLED) {
      onFulfilled(this.value);
    }

    if (this.status == REJECTED) {
      onRejected(this.reason);
    }
  }
}

// 本文是在 node 环境进行代码测试,遵循 commonJs 规范
module.exports = _Promise;
测试代码
const _Promise = require('./_Promise.js');

const promise = new _Promise((resolve, reject) => {
  throw new Error('error');

  resolve('ok');
  reject('不ok');
});

promise.then(val => {
  console.log(val, 'success');
}, reason => {
  console.log(reason, 'fail');
});

// error file

考虑异步执行 resolve 或 reject

promise 的状态往往都是异步更改的,比如请求接口成功后再调用 resolve 去改变 promise 的状态,如果按我们上面的实现,还没等到状态改变,then 方法就已经执行了,此时状态还是 pending,没有任何反应嘛。

所以我们要做一件事情,代码执行到 .then 时,我们不立即根据 promise 状态执行回调,而是把回调保存起来,状态改变的时候再来调用它「发布订阅」

// 考虑异步调用 resolve/reject 的版本
//   @1 then 的时候,promise 实例状态还没改变,此时做回调方法收集
//   @2 异步调用 resolve/reject 时,挨个执行回调『可能会有多个回调』
class _Promise {
  constructor(executor) {
    // ...
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = (value) => {
      // ...
      // 执行收集到的成功回调
      this.onResolvedCallbacks.forEach(cb => cb(this.value));
    }

    const reject = (reason) => {
      // ...  
      // 执行收集到的失败回调
      this.onRejectedCallbacks.forEach(cb => cb(this.reason));
    }
    // ...
  }

  then(onFulfilled, onRejected) {
    // ...
    if (this.status == PENDING) {
      // 异步改变 promise 状态时,回调方法收集
      this.onResolvedCallbacks.push(onFulfilled);
      this.onRejectedCallbacks.push(onRejected);
    }
  }
}

// 本文是在 node 环境进行代码测试,遵循 commonJs 规范
module.exports = _Promise;
完整代码
// 考虑异步调用 resolve/reject 的版本
const PENDING = 'PENDING'; // 等待态「默认状态」
const FULFILLED = 'FULFILLED'; // 成功态
const REJECTED = 'REJECTED'; // 失败态

class _Promise {
  constructor(executor) {
    this.status = PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = (value) => {
      if (this.status !== PENDING) return;
      this.value = value;
      this.status = FULFILLED;
      // 执行收集到的成功回调
      this.onResolvedCallbacks.forEach(cb => cb(this.value));
    }

    const reject = (reason) => {
      if (this.status !== PENDING) return;
      this.reason = reason;
      this.status = REJECTED;
      // 执行收集到的失败回调
      this.onRejectedCallbacks.forEach(cb => cb(this.reason));
    }

    try {
      executor(resolve, reject);
    } catch(e) {
      reject(e);
    }
  }

  // 原型上的 then 方法
  then(onFulfilled, onRejected) {
    if (this.status == FULFILLED) {
      onFulfilled(this.value);
    }

    if (this.status == REJECTED) {
      onRejected(this.reason);
    }

    if (this.status == PENDING) {
      // 异步改变 promise 状态时,回调方法收集
      this.onResolvedCallbacks.push(onFulfilled);
      this.onRejectedCallbacks.push(onRejected);
    }
  }
}

// 本文是在 node 环境进行代码测试,遵循 commonJs 规范
module.exports = _Promise;

测试代码
const promise = new _Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  }, 1000)
});

promise.then(val => {
  console.log(val, 'success');
}, reason => {
  console.log(reason, 'fail');
});

// 多回调场景
promise.then(val => {
  console.log(val, 'success');
}, reason => {
  console.log(reason, 'fail');
});

// 1s 后输出两个 "1 success"

实现链式调用

// 前置知识
//   @1 then 返回的总是一个包装好的 promise 对象,传递给下一个 then 方法,遵循如下规则:
//      + 如果 then 内部返回的就是一个 promise,则采用返回的 promise 的状态和值往下传递
//      + 如果 then 返回一个普通值『不是 promise』,则相当于返回 Promise.resolve(val)
//      + 如果 then 方法语法报错或抛出一个错误,则相当于 Promise.reject(error)
//   @2 实现链式调用的关键在于,总是返回一个全新的 promise2 对象,来保证状态可以正常切换
//      + 比如在成功回调中返回了一个失败的 promise,如果返回 this『原 promise 对象』
//        就会出现状态不能从成功改到失败的状况。
//   @3 返回的 promsie2 中的 executor 函数是立即执行的,而我们也刚好要拿到上一轮的 
//      promise 结果,来决定是调用 promise2.reslove 还是 promise2.reject,用 x 来
//      表示上一轮的返回结果,我们可以取巧的把 then 内部代码都搬到 promise2 的 executor 
//      方法中来执行,这样就可以直接根据返回的结果 x,判断如何更改 promise2 的状态,
//      并且把 x 往下传递 
//   @4 我们声明一个 resolvePromise 方法,用来处理往下传递的 x

class _Promise {
  constructor(executor) {
    const resolve = (value) => {
      // ...
      this.onResolvedCallbacks.forEach(cb => cb());
    }

    const reject = (reason) => {
      // ...
      this.onRejectedCallbacks.forEach(cb => cb());
    }
    // ... 
  }

  // 原型上的 then 方法
  then(onFulfilled, onRejected) {
    let promise2 = new _Promise((resolve, reject) => {
      if (this.status == FULFILLED) {
        // 注意,这里使用 promise2,必须异步获取(new 的过程中使用 实例)
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            // 根据 x 类型,判断 promise2 的状态和值
            resolvePromise(x, promise2, resolve, reject); 
          } catch(e) {
            // 上轮链式执行报错, 走本轮的失败回调
            reject(e); 
          }
        }, 0)
      }
  
      if (this.status == REJECTED) {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(x, promise2, resolve, reject); 
          } catch(e) {
            reject(e); 
          }
        }, 0);
      }
  
      if (this.status == PENDING) {
        this.onResolvedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
    
              resolvePromise(x, promise2, resolve, reject); 
            } catch(e) {
              reject(e); 
            }
          }, 0)
        });
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason);
              resolvePromise(x, promise2, resolve, reject);
            } catch(e) {
              reject(e); 
            }
          }, 0);
        });
      }
    });

    return promise2;
  }
}

// 本文是在 node 环境进行代码测试,遵循 commonJs 规范
module.exports = _Promise;
完整代码
// 实现链式调用
const resolvePromise = require('./resolvePromise');
const PENDING = 'PENDING'; // 等待态「默认状态」
const FULFILLED = 'FULFILLED'; // 成功态
const REJECTED = 'REJECTED'; // 失败态

class _Promise {
  constructor(executor) {
    this.status = PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = (value) => {
      if (this.status !== PENDING) return;
      this.value = value;
      this.status = FULFILLED;
      this.onResolvedCallbacks.forEach(cb => cb());
    }

    const reject = (reason) => {
      if (this.status !== PENDING) return;
      this.reason = reason;
      this.status = REJECTED;
      this.onRejectedCallbacks.forEach(cb => cb());
    }

    try {
      executor(resolve, reject);
    } catch(e) {
      reject(e);
    }
  }

  // 原型上的 then 方法
  then(onFulfilled, onRejected) {
    let promise2 = new _Promise((resolve, reject) => {
      if (this.status == FULFILLED) {
        // 注意,这里使用 promise2,必须异步获取(new 的过程中使用 实例)
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            // 根据 x 类型,判断 promise2 的状态和值
            resolvePromise(x, promise2, resolve, reject); 
          } catch(e) {
            // 上轮链式执行报错, 走本轮的失败回调
            reject(e); 
          }
        }, 0)
      }
  
      if (this.status == REJECTED) {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(x, promise2, resolve, reject); 
          } catch(e) {
            reject(e); 
          }
        }, 0);
      }
  
      if (this.status == PENDING) {
        this.onResolvedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
    
              resolvePromise(x, promise2, resolve, reject); 
            } catch(e) {
              reject(e); 
            }
          }, 0)
        });
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason);
              resolvePromise(x, promise2, resolve, reject); 
            } catch(e) {
              reject(e); 
            }
          }, 0);
        });
      }
    });

    return promise2;
  }
}

// 本文是在 node 环境进行代码测试,遵循 commonJs 规范
module.exports = _Promise;
// @1 根据上一轮 then 的返回结果 x,来判断如何往下传递新 promise 对象『promise2』的状态和值
//  + 如果 x 就是一个 promise,则采用返回的 promise 的状态和值往下传递
//  + 如果 x 是一个普通值『不是 promise』,返回 promise2.resolve(val)
// @2 我们还需要考虑别人实现的 promise 的各种奇葩情况💦

function resolvePromise(x, promise2, resolve, reject) {
  // 根据规范 2.3.1,如果返回值 x 就是 promise2,这就会导致循环引用
  // let promise2 = new Promise(resolve => resolve('ok')).then(data => {
  //   return promise2;
  // })
  if (x === promise2) {
    return reject(new TypeError('Chaining cycle detected for promise'));
  }

  // 根据规范2.3.3.3 为了防止某些库可以调用两次 resolve 或 reject「不规范的实现」,加个锁
  let called = false; 
  // 严格按照规范 2.3.3
  // 判断 promise 是一个 thenable 的对象类型还是普通值
  if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
   try {
    // 是对象,判断是否存在 x.then,且 then 是否为一个函数
    let then = x.then;

    if (then && typeof then === 'function') {
      // thenable 对象成立,我们认为 x 就是个 promise 对象,这时根据 x 的状态决定接下来传递的 promise2 的状态
      // 这里调用 then 时,不使用 x.then 的原因是防止有些库,取第二次 then 属性抛出一个错误
      // 根据 2.3.3.3
      then.call(x, y => {
        if (called) return;
        called = true;
        // 成功回调 y 有可能还是一个 promise「套娃」,要递归解析,直到得到普通值
        resolvePromise(y, promise2, resolve, reject);
      }, r => {
        if (called) return;
        called = true;
        // 失败回调
        reject(r);
      });

    } else {
      // 比如 x 是 { then: 1 },这时候会把 x 作为值,promise2 改为成功态
      resolve(x);
    }
   } catch (e) {
     // 如果某些库劫持了 x.then 的 get 方法,主动抛出一个错误
     reject(e);
   }
  } else {
    // 是值类型
    resolve(x);
  }
}

module.exports = resolvePromise;

测试代码
let promise = new _Promise(resolve => resolve('ok')).then(data => {
  return new _Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(100);
    }, 1000);
  });
})

promise.then(data => {
  console.log(data);
}, reason => {
  console.log('err', reason)
});

// 1s 后输出 100

实现值的穿透

如果在 then 链式调用中,我们省略了某个成功(onFulfilled)或者失败(onRejected)的方法,这时候会进行值的穿透。

let promise = new Promise(resolve => {
  resolve('ok')
}).then().then().then(data => {
  console.log(data);
})
// ok

这个实现就较为简单了,我们只需要判断 onFulfilled 或者 onRejected 是不是一个函数类型,做对应处理即可。

 then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled == 'function' ? onFulfilled : val => val;
    onRejected = typeof onRejected == 'function' ? onRejected : reason => { throw reason };
    // ...
 }
测试代码
let promise = new _Promise((resolve, reject) => {
  // resolve('ok')
  // reject('fail');
  throw new Error('error');
}).then().then().then(data => {
  console.log(data);
}, reason => {
  console.log('reason', reason);
})

// reason  error

至此,promise A+ 规范中的所有代码已经实现完毕,catch、finally、all 等方法不属于规范,只是一些扩展方法,详见手撕 promise 扩展方法

promise A+ 规范测试

官方地址

// 测试方法
//   @1 在我们实现的 _Promise 文件底部声明以下静态方法,它会帮我们测试 dfd 对象的 
//      promise、resolve、reject 是否符合规范 
//   @2 安装 promises-aplus-tests 包,package 中添加 scripts: 
//      promises-aplus-tests _Promise4/_Promise4.js (自己的源码路径)

_Promise.deferred = function() {
  let dfd = {};

  dfd.promise = new _Promise((resolve, reject) => {
    dfd.resolve = resolve;
    dfd.reject = reject;
  });

  return dfd;
}

package.json 中添加一个命令

  "scripts": {
    "test": "promises-aplus-tests _Promise4/_Promise4.js"
  }

执行 npm run test

完整代码

完整代码
// _Promise4.js
const resolvePromise = require('./resolvePromise');
const PENDING = 'PENDING'; // 等待态「默认状态」
const FULFILLED = 'FULFILLED'; // 成功态
const REJECTED = 'REJECTED'; // 失败态

class _Promise {
  constructor(executor) {
    this.status = PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = (value) => {
      if (this.status !== PENDING) return;
      this.value = value;
      this.status = FULFILLED;
      this.onResolvedCallbacks.forEach(cb => cb());
    }

    const reject = (reason) => {
      if (this.status !== PENDING) return;
      this.reason = reason;
      this.status = REJECTED;
      this.onRejectedCallbacks.forEach(cb => cb());
    }

    try {
      executor(resolve, reject);
    } catch(e) {
      reject(e);
    }
  }

  // 原型上的 then 方法
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled == 'function' ? onFulfilled : val => val;
    onRejected = typeof onRejected == 'function' ? onRejected : reason => { throw reason };
    let promise2 = new _Promise((resolve, reject) => {
      if (this.status == FULFILLED) {
        // 注意,这里使用 promise2,必须异步获取(new 的过程中使用 实例)
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            resolvePromise(x, promise2, resolve, reject); // 根据 x 类型,判断 promise2 的状态和值
          } catch(e) {
            reject(e); // 上轮链式执行报错, 走本轮的失败回调
          }
        }, 0)
      }
  
      if (this.status == REJECTED) {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(x, promise2, resolve, reject); // 根据 x 类型,判断 promise2 的状态和值
          } catch(e) {
            reject(e); // 上轮链式执行报错, 走本轮的失败回调
          }
        }, 0);
      }
  
      if (this.status == PENDING) {
        this.onResolvedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
    
              resolvePromise(x, promise2, resolve, reject); // 根据 x 类型,判断 promise2 的状态和值
            } catch(e) {
              reject(e); // 上轮链式执行报错, 走本轮的失败回调
            }
          }, 0)
        });
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason);
              resolvePromise(x, promise2, resolve, reject); // 根据 x 类型,判断 promise2 的状态和值
            } catch(e) {
              reject(e); // 上轮链式执行报错, 走本轮的失败回调
            }
          }, 0);
        });
      }
    });

    return promise2;
  }
}

_Promise.deferred = function() {
  let dfd = {};

  dfd.promise = new _Promise((resolve, reject) => {
    dfd.resolve = resolve;
    dfd.reject = reject;
  });

  return dfd;
}

// 本文是在 node 环境进行代码测试,遵循 commonJs 规范
module.exports = _Promise;

// @1 根据上一轮 then 的返回结果 x,来判断如何往下传递新 promise 对象『promise2』的状态和值
//  + 如果 x 就是一个 promise,则采用返回的 promise 的状态和值往下传递
//  + 如果 x 是一个普通值『不是 promise』,返回 promise2.resolve(val)
// @2 我们还需要考虑别人实现的 promise 的各种奇葩情况💦

function resolvePromise(x, promise2, resolve, reject) {
  // 根据规范 2.3.1,如果返回值 x 就是 promise2,这就会导致循环引用
  // let promise2 = new Promise(resolve => resolve('ok')).then(data => {
  //   return promise2;
  // })
  if (x === promise2) {
    return reject(new TypeError('Chaining cycle detected for promise'));
  }

  // 严格按照规范 2.3.3
  // 判断 promise 是一个 thenable 的对象类型还是普通值
  if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
    // 根据规范2.3.3.3 为了防止某些库可以调用两次 resolve 或 reject「不规范的实现」,加个锁
    let called = false;
    try {
      // 是对象,判断是否存在 x.then,且 then 是否为一个函数
      let then = x.then;

      if (typeof then === 'function') {
        // thenable 对象成立,我们认为 x 就是个 promise 对象,这时根据 x 的状态决定接下来传递的 promise2 的状态
        // 这里调用 then 时,不使用 x.then 的原因是防止有些库,取第二次 then 属性抛出一个错误
        // 根据 2.3.3.3
        then.call(x, y => {
          if (called) return;
          called = true;
          // 成功回调 y 有可能还是一个 promise「套娃」,要递归解析,直到得到普通值
          resolvePromise(y, promise2, resolve, reject);
        }, r => {
          if (called) return;
          called = true;
          // 失败回调
          reject(r);
        });

      } else {
        // 比如 x 是 { then: 1 },这时候会把 x 作为值,promise2 改为成功态
        // 不存在调用多次情况 他不是一个 promise
        resolve(x);
      }
    } catch (e) {
      // 如果某些库劫持了 x.then 的 get 方法,主动抛出一个错误
      // 不能失败了再失败 两次调用只走一次
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
    // 是值类型 不存在调用多次情况 他不是一个 promise
    resolve(x);
  }
}

module.exports = resolvePromise;

思考

如果 constructor 中的 resolve 方法继续接收一个异步返回结果的 promise 呢,我们在 resolve 中默认把 value 当成值类型直接赋值了

let p = new _Promise((resolve, reject) => {
  resolve(new _Promise(resolve => {
    setTimeout(() => {
      resolve(100);
    }, 1000)
  }));
}).then(val => {
  console.log(val);
});

// _Promise { status: 'PENDING', value: undefined, reason: undefined }

不过这个不属于 promise A+ 规范,我们在下一篇文章实现静态方法 resolve 时,会对它进行修复。 传送阵: 手撕 promise 扩展方法