手写Promise-符合MDN描述和promise/A+规范

207 阅读4分钟

为什么还要实现promise???,因为现有的手写方案,有的不符合MDN的描述、有的不符合promise/A+规范、有的不能兼容同步和异步两种内部函数的使用形式。故此,还是自己动手、丰衣足食。以下是测试用例及实现源码

以下测试用例均已通过:

用例1:测试参数内为同步逻辑时,是否可用. 

new PromiseDemo(function(resolve, reject){
   resolve('同步')
})
  .then(function(res) {
     console.log(res); // 同步
  });  

  

用例2:测试resolve参数为promise,这种特殊情况时是否可用. 

new PromiseDemo(function(resolve, reject){
  resolve(new PromiseDemo(function(reso) {
    reso('resolve-promose')
  }))
})
  .then(function(res) {
     console.log(res); // resolve-promose
  });  
  

用例3:测试错误捕捉. 

new PromiseDemo(function(resolve, reject){
  throw new Error('错误');
  resolve(123);
})
  .then(function(res) {
    console.log(res);
  }).catch(function(err) {
    console.log(err);
  });
  
// Error: 错误 
// at <anonymous>:2:9  
// at new PromiseDemo (<anonymous>:9:13)  
// at <anonymous>:1:1  

用例4:测试参数内为异步逻辑时,是否可用. 

new PromiseDemo(function(resolve, rej) {
  setTimeout(function() {
    resolve(1);
  }, 10)
})
  .then(function(res) {
    console.log(res); // 1
  }, function(err) {
    console.log(err);
  });  
  

用例5:测试all是否可用    

PromiseDemo.all([
  fetch('https://cdn.bootcss.com/vue/2.5.16/vue.min.js',{method: 'GET'}).then((r) => r.text()).then((r) => r.slice(6,21)),  
  fetch('https://unpkg.com/react@16/umd/react.production.min.js', {method: 'GET'}).then((r) => r.text()).then((r) => r.slice(13,27)),]
)
  .then(function(res) {
    console.log(res); // [" Vue.js v2.5.16", "React v16.14.0"]
  });  

用例6:测试race是否正确执行   

PromiseDemo.race([
  fetch('https://cdn.bootcss.com/vue/2.5.16/vue.min.js', {method: 'GET'}).then((r) => r.text()).then((r) => r.slice(6,21)),  
  fetch('https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js', {method: 'GET'}).then((t) => t.text()).then((r) => r.slice(3,17))]).then(function(res) {  console.log(res); // Vue.js v2.5.16});  
  

同用例6:换个请求资源,测试race是否正确执行
PromiseDemo.race([
  fetch('https://unpkg.com/react@16/umd/react.production.min.js', {method: 'GET'}).then((r) => r.text()).then((r) => r.slice(13,27)),  
  fetch('https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js', {method: 'GET'}).then((t) => t.text()).then((r) => r.slice(3,17))]
)
  .then(function(res) {
    console.log(res); // jQuery v3.4.1
  });

实现源码

感兴趣的同学,建议观码顺序如下:

1.constructor

2.resolve

3.then

4.reject

5.catch

6.finally

7.all

8.race

根据MDN描述,resolve、reject、all、race为静态方法,所以写了static

根据Promise/A+规范,resolve的参数可能是promise,故在resolve内部还要判断是否是PromiseDemo的实例,再进行逻辑拆分。

class PromiseDemo {    /**  构造函数   *   初始化then事件队列、catch事件队列、状态status   *   执行参数fn,传入resolve和reject  */    constructor(fn) {    if (fn && typeof fn !== 'function') throw new Error(Parameter is not a function);    this.thenQue = []    this.catchQue = []    this.status = '<pending>';    try {      fn && fn(PromiseDemo.resolve.bind(this), PromiseDemo.reject.bind(this));    } catch(e) {      PromiseDemo.reject.bind(this)(e);    }  }    /**  resolve函数   *   主要作用为执行thenQue队列中的函数   *   如果状态为fulfilled,则不再执行   *   如果resolve的参数不是PromiseDemo实例,则正常执行,这也是我们常用的场景   *   如果resolve的参数是PromiseDemo实例,则将实例内部的resolve值或者then内的函数返回值暴露出来,连接上外部的resolve执行,这样可以接着用外部的then函数队列,依次执行。  *   如果resolve一开始调用时,没有值,则返回一个PromiseDemo实例,因为存在用法Promise.resolve().then()   *   函数内使用了setTimeout,是为了将后续逻辑加入下一个宏任务,此时,then将优先执行,提前将函数逻辑加入thenQue队列   *   注意,promise是微任务,此处用setTimeout是为了实现promise效果,因为浏览器环境下,除了mutation和promise外,没有可以异步的函数了。  */    static resolve(data) {    if (this.status === '<fulfilled>') return;    this.value = data;    const isInstance = data instanceof PromiseDemo;    if (!isInstance) {      setTimeout(() => {        try {          this.thenQue.forEach((item) => {            this.value = item(this.value);          });          this.thenQue = [];          this.status = '<fulfilled>';        } catch (e) {          this.status = '<rejected>';          PromiseDemo.reject.bind(this)(e);        }      });    } else {      data.then((res) => {        PromiseDemo.resolve.bind(this)(res);      }).catch((err) => {        PromiseDemo.reject.bind(this)(err);      });    }    if (!data) {      return new PromiseDemo();    }  }        /**  reject函数   *   主要作用是执行catchQue事件队列中的catch事件函数  */    static reject(err) {    if (this.status === '<rejected>') return;    this.error = err;    let count;    setTimeout(() => {      try {        this.catchQue.forEach((item, index) => {          count = index;          this.error = item(this.error);        });        this.catchQue = [];        this.status = '<rejected>';      } catch (e) {        this.catchQue = this.catchQue.slice(count+1);        PromiseDemo.reject.bind(this)(e);      }    });    if (!err) {      return new PromiseDemo();    }  }        /**  then函数   *   主要作用为将then内的函数全部收集起来,组成then事件队列thenQue  */    then(onResolve, onReject) {    if (typeof onReject === 'function') {      this.catchQue.push(onReject);    }    typeof onResolve === 'function' && this.thenQue.push(onResolve);    return this;  }        /**  catch函数   *   主要作用为将catch内的函数全部收集起来,组成catch事件队列catchQue  */    catch(fn) {    this.catchQue.push(fn);    return this;  }    /**  finally函数   *   将fn推入队列,无论事件队列thenQue执行,还是catchQue执行,最后都可以执行到  */    finally(fn) {    this.thenQue.push(fn);    this.catchQue.push(fn);  }        /** all函数   *   参数为数组,数组每一项都是PromiseDemo的实例   *   对每项添加then方法,则当执行到then内部方法时,判断是否全部promise都已执行完,若都已执行完毕,则整体resolve  */    static all(arr) {    const resArr = [];    const length = arr.length;    let resCount = 0;    let that;    try {      arr.forEach(function(item, index) {        item.then(function(res) {          resArr[index] = res;          resCount++;          if (resCount === length) {            PromiseDemo.resolve.bind(that)(resArr);          }        })      });    } catch (e) {      PromiseDemo.reject.bind(that)(e);    }    that = new PromiseDemo();    return that;  }        /**  race函数   *   只要有一个执行完毕,则直接整体resolve,当另一个也执行到resolve时,因status已发生改变,则不会再向下执行  */  static race(arr) {    let that;    try {      arr.forEach(function(item, index) {        item.then(function(res) {          PromiseDemo.resolve.bind(that)(res);        })      });    } catch (e) {      PromiseDemo.reject.bind(that)(e);    }    that = new PromiseDemo();    return that;  }}