天天看點

Promise實作-符合MDN描述和promise/A+規範

為什麼還要實作promise???隻因之前方案不夠好,有的不符合MDN的描述、有的不符合promise/A+規範、有的不能相容同步和異步兩種内部函數的使用形式。故此,還是自己動手、豐衣足食,實作了PromiseDemo,以下是測試用例及實作源碼。

以下測試用例均已認證:

// 用例1:同步情況下resolve

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:異步resolve

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

});  
複制代碼      

// 用例7: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

});  
複制代碼      

PromiseDemo實作源碼

感興趣的同學,建議觀碼順序:

1.constructor

2.resolve

3.then

4.reject

5.catch

6.finally

7.all

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;
  }

}複制代碼