手撸一个符合A+ 标准的Promise

106 阅读2分钟

实现Promise

class APromise {
    static PENDING = 'pending';
    static REJECTED = 'rejected';
    static RESOLVED = 'resolved';
    constructor(fun) {
      this.PromiseState = APromise.PENDING;
      this.PromiseResult = undefined;
      this.resCbkList = [];
      this.rejCbkList = [];
      try {
        fun(this.resolve.bind(this), this.reject.bind(this));
      } catch (e) {
        this.reject(e);
      }
    }
  
    resolve(val) {
      if (this.PromiseState === APromise.PENDING) {
        this.PromiseState = APromise.RESOLVED;
        this.PromiseResult = val;
        this.resCbkList.forEach(fn=>{
            fn()
        })
      }
    }
  
    reject(reason) {
      if (this.PromiseState === APromise.PENDING) {
        this.PromiseState = APromise.REJECTED;
        this.PromiseResult = reason;
        this.rejCbkList.forEach(fn=>{
            fn()
        })
      }
    }
  
    then(resolveCbk, rejectCbk) {
      const resCbk = typeof resolveCbk === 'function' ? resolveCbk : (val) => val;
      const rejCbk =
        typeof rejectCbk === 'function'
          ? rejectCbk
          : (reason) => {
              throw reason
            };
      const p2 = new APromise((resolve, reject) => {
        if (this.PromiseState === APromise.RESOLVED) {
          setTimeout(() => {
            try {
              const x = resCbk(this.PromiseResult);
              resolvePromise(x, p2, resolve, reject);
            } catch (err) {
              reject(err);
            }
          }, 0);
        }
        if (this.PromiseState === APromise.REJECTED) {
          setTimeout(() => {
            try {
              const x = rejCbk(this.PromiseResult);
              resolvePromise(x, p2, resolve, reject);
            } catch (err) {
              reject(err);
            }
          }, 0);
        }
        if (this.PromiseState === APromise.PENDING) {
          this.rejCbkList.push(() => {
            setTimeout(() => {
              try {
                const x = rejCbk(this.PromiseResult);
                resolvePromise(x, p2, resolve, reject);
              } catch (err) {
                reject(err);
              }
            }, 0);
          });
          this.resCbkList.push(() => {
            setTimeout(() => {
              try {
                const x = resCbk(this.PromiseResult);
                resolvePromise(x, p2, resolve, reject);
              } catch (err) {
                reject(err);
              }
            }, 0);
          });
        }
      });
      return p2;
    }
  }
  
  function resolvePromise(x, p2, resolve, reject) {
    if (x === p2) {
     throw new TypeError('不能返回同一个promise');
    }
    if (x instanceof APromise) {
      x.then((y) => {
        resolvePromise(y, p2, resolve, reject);
      }, reject);
    } else if ((typeof x === 'object' || typeof x === 'function') && x !== null) {
      let then;
      try {
        then = x.then;
      } catch (err) {
       return reject(err);
      }
      if (typeof then === 'function') {
        let called = false;
        try{
            then.call(
                x,
                (y) => {
                    if (called) return;
                    called = true;
                    resolvePromise(y, p2, resolve, reject);
                },
                (reason) => {
                  if (called) return;
                  called = true;
                  reject(reason);
                },
              );
        }catch(err) {
            if (called) return;
            called = true;
            reject(err);
        }
      } else {
        resolve(x);
      }
    } else {
      resolve(x);
    }
  }

  APromise.deferred = function () {
    let result = {};
    result.promise = new APromise((resolve, reject) => {
        result.resolve = resolve;
        result.reject = reject;
    });
    return result;
}


module.exports = APromise

如何检验实现的Promise是否符合规范

可以使用Promises/A+官方的测试工具 promises-aplus-tests 来对我们的myPromise进行测试。

如何使用promises-aplus-tests

1、先安装

npm install promises-aplus-tests -D

2、使用 CommonJS 对外暴露 myPromise 类并实现一个实现静态方法 deferred

// 以APromise为例
class APromise { ... } 
function resolvePromise(x, p2, resolve, reject) {
 APromise.deferred = function () {
    let result = {};
    result.promise = new APromise((resolve, reject) => {
        result.resolve = resolve;
        result.reject = reject;
    });
    return result;
}
module.exports = APromise

3、在package.json中加入如下命令

{ 
    "scripts": { "test": "./myPromise.js" } 
}

4、运行npm run start即可

0d5360d514fcedac7b0f2c9458cac6e.jpg

5、可能遇到的问题

1d8640ab8b0517e92962f940ac54ea9.jpg 这是因为在package.json文件中将type值设为了module,当设置为“module”时,所在项目中(不包含node_modules)所有.js文件将被视为EsModule类型文件。所以我们上面用commonjs规范导出类就会出错 。将type属性移除即可,type属性默认为commonjs规范

0d5360d514fcedac7b0f2c9458cac6e.jpg

参考文章

手把手一行一行代码教你“手写Promise“,完美通过 Promises/A+ 官方872个测试用例 [译]Promise/A+ 规范
Promise A+标准原文