用 class 寫法完整實現一個 Promise

1.前言

本文分析 Promise 特性的瞭解,完整實現了 Promise 全部功能。沒有參考原生 Promise 的寫法,本身根據思路一步一步完成以及描述,每一個構建模塊由:一、Promise 特性描述;二、實現特性的完整思路(分析一波) 三、項目代碼;四、功能測試代碼 幾個部分組成。大體用到的知識有: 一、變量私有化;二、訂閱發佈模式;三、eventloop 理解;四、Promise特性;五、class 特性;六、對象類型的斷定... 算了不寫了 強行塞這麼多我也是夠拼的

你能夠點我看源碼點我看原文地址javascript

2.Promise 特徵分析

  • Promise 有三種狀態: pending(執行中)、 fulfilled(成功執行)、settled(異常捕獲);
  • Promise 能夠經過 new 關鍵字建立一個 未完成的 Promise;
  • Promise 能夠直接經過 Promise.resolve 建立一個成功完成的 Promise 對象;
  • Promise 能夠直接經過 Promise.reject 建立一個異常狀態的 Promise 對象;
  • 經過 new 關鍵字建立的 Promise 方法裏若是出現錯誤,會被 Promise 的 reject 捕獲;
  • Promise.resolve / Promise.reject 接收 thenable 對象和 Promise 對象的處理方式;
  • 當沒有錯誤處理時的,全局的 Promise 拒絕處理;
  • 串聯 Promise 以及 Promise 鏈返回值;
  • Promise.all Promise.race;

3.Promise 的實現

  • 狀態碼私有化

    開始以前討論一波 class 私有屬性的實現,我的想到的方案以下:java

    1.經過閉包,將變量存放在 construct 方法裏;弊端,全部的其餘的對象方法必須在 construct 內定義(NO)。node

    2.經過在定義 Promise 的環境下定義一個 Map,根據當前對象索引去獲取相應的私有值;弊端,由於 Map 的 key 是強引用,當定義的 Promise 不用時也不會被內存回收(NO);jquery

    3.經過在定義 Promise 的環境下定義一個 WeakMap,根據當前對象索引去獲取相應的私有值; 優點,木有以上兩種劣勢(不寫點什麼感受難受);git

    說了這麼多那麼我們要用第三種方法嗎?NO,原生 [[PromiseState]] 是一個內部屬性,不暴露在 Promise 上,可是經過瀏覽器的控制檯能夠看到,用第三種方式模仿並不能直觀的在控制檯看到,因此我決定仍是不要做爲私有變量出現,可是把枚舉特性幹掉了 僞裝他是私有變量 內心好過一點 所以你就能看到下面的代碼;es6

const PENDDING = 'pendding';// 等待狀態
const FULFILLED = 'resolved';// 成功操做狀態
const REJECTED = 'rejected';// 捕獲錯誤狀態

class MyPromise{
  
  constructor(handler){
    // 數據初始化
    this.init();
  }
  
  // 數據初始化
  init(){
    Object.defineProperties(this,{
      '[[PromiseState]]': {
        value: PENDDING,
        writable: true,
        enumerable: false
      },
      '[[PromiseValue]]': {
        value: undefined,
        writable: true,
        enumerable: false
      },
      'thenQueue':{
        value: [],
        writable: true,
        enumerable: false
      },
      'catchQueue':{
        value: [],
        writable: true,
        enumerable: false
      }
    })
  }
  // 獲取當前狀態
  getPromiseState (){
    return this['[[PromiseState]]'];
  }
  // 設置當前狀態
  setPromiseState (state) {
    Object.defineProperty(this, '[[PromiseState]]', {
      value: state,
      writable: false
    })
  }

  // 獲取當前值
  getPromiseValue (){
    return this['[[PromiseValue]]'];
  }
  // 設置當前值
  setPromiseValue (val) {
    Object.defineProperty(this, '[[PromiseValue]]', {
      value: val
    })
  }
}
  • 建立一個未完成狀態的Promise

    函數調用過程分析:github

    1. 使用者經過 new 關鍵字傳入一個方法;
    2. 方法有兩個參數 resolvereject 兩個方法
    3. 當傳入的方法調用 resolve 時,狀態變爲 fulfilled,有且只有接收一次 resolve 裏的方法裏的值做爲 [[PromiseValue]],供該 Promise 對象下的 then 方法使用;
    4. 當傳入的方法調用 reject 時,狀態變爲 rejected,有且只有接收一次 reject 裏的方法裏的值做爲 [[PromiseValue]],供該 Promise 對象下的 catch 方法使用;

代碼思路:數組

  1. 首先傳入的函數應該在 construct 方法裏進行調用;
  2. 因具有一個存放待執行成功操做方法的隊列,一個存放捕獲異常方法的隊列。
  3. resolve 方法下處理的問題是:promise

    一、判斷當前狀態是不是等待狀態,若是不是則啥也不幹,若是是走第二步
    
    二、修改```[[PromiseState]]```爲FULFILLED;
    
    三、將 ```[[PromiseValue]]``` 賦值爲方法傳遞進來的參數; 
    
    四、成功操做方法的隊列在 eventloop 結束後<sup>①</sup>依次調用而後清空,捕獲異常方法的隊列清空;
  4. reject 方法基本就不贅述啦......
  5. then 方法:瀏覽器

    一、 判斷當前狀態是否爲等待,是等待進行第 2 步,不然進行第 3 步;
    
    二、 加入成功操做方法隊列;
    
    三、 當前eventloop 結束異步調用;
  6. catch 方法不贅述

ps: 注①由於沒法將任務插入 microtask 中,就用 eventloop結束做爲替代;

// 事件循環最後執行
  const eventLoopEndRun = function (handler){
    setImmediate(()=>{
      handler()
    })
  }
  // ...

  class MyPromise{
  
    constructor(handler){
      // ...
      
      // 方法傳遞,經過 bind 保持兩個方法對當前對象的引用
      handler(this.resolve.bind(this), this.reject.bind(this));
    }

    // ...

    // 清空等待隊列
    clearQueue (currentState) {
      
      const doQueue = currentState === REJECTED ? this.catchQueue : this.thenQueue;
      const promiseData = this.getPromiseValue();

      doQueue.forEach(queueHandler=>queueHandler(promiseData));
      this.catchQueue = [];
      this.thenQueue = []
    }

    // 狀態改變方法
    changeStateHandler (currentState, data){

      this.setPromiseState(currentState);
      this.setPromiseValue(data);
      setImmediate(()=>{this.clearQueue(currentState)});
      
      // 保持狀態只能改變一次
      this.changeStateHandler = null;
      this.setPromiseState = null;
      this.setPromiseValue = null;
    }

    // 不解釋
    resolve (data) {
      this.changeStateHandler && this.changeStateHandler(FULFILLED, data);
    }
    // 不解釋
    reject (err) {
      this.changeStateHandler && this.changeStateHandler(REJECTED, err);
    }

    // 不解釋
    then(thenHandler){
      
      const currentState = this.getPromiseState();
      const promiseData = this.getPromiseValue();

      if (currentState === FULFILLED) thenHandler(promiseData);
      else if (currentState === PENDDING) this.thenQueue.push(thenHandler);
    }

    // 不解釋
    catch(catchHandler){
      
      const currentState = this.getPromiseState();
      const promiseData = this.getPromiseValue();

      if (currentState === REJECTED) catchHandler(promiseData);
      else if (currentState === PENDDING) this.catchQueue.push(catchHandler);
    }
  }

  // 測試方法


  const test1 = new MyPromise((resolve,reject)=>{
    setTimeout(()=>{
      resolve('2s 後輸出了我');
    }, 2000)
  });

  const test2 = new MyPromise((resolve,reject)=>{
    setTimeout(()=>{
      reject('我出錯啦!')
    }, 2000)
  })

  test1.then(data=>console.log(data));
  test1.catch(err=>console.log(err));
  test2.then(data=>console.log(data));
  test2.catch(err=>console.log(err));
  console.log("我是最先的");
  • 建立一個完成狀態的Promise

    經過 Promise.resolve() 建立一個成功操做的 Promise 對象; Promise.reject() 建立一個捕獲錯誤的 Promise 對象,new 關鍵字傳入的方法體有報錯,會直接被 reject 捕獲;

    分析一波:

    1. 能直接調用的方法,妥妥應該的是一個靜態方法;
    2. 調用以後要生成一個新的 Promise 對象;
    3. 因此我們就要分兩步走 1,建立一個 Promise 對象,而後調用其 resolve 方法.
    4. 由於實例化的對象不能獲取寄幾的 static 方法
    5. 經過 try+catch 捕獲 handler 異常,並經過 reject 進行拋出;
// ...
  // construct 方法新增一個類型,當 new 關鍵字進來傳遞的不是一個函數,我們一樣在 eventLoop 結束拋出一個錯誤
  if(Object.prototype.toString.call(handler) !== "[object Function]"){
    eventLoopEndRun(()=>{
      throw new Error(`MyPromise resolver ${typeof handler} is not a function`)
    })
  } else {
    // 方法傳遞,this指向會變,經過 bind 保持兩個方法對當前對象的引用
    // 固然也能夠這麼玩:data=>this.resolve(data)
    try{
      handler(this.resolve.bind(this), this.reject.bind(this));
    } catch(err) {
      this.reject(err);
    }
  }

  // ...
  // 不解釋
  static resolve (data) {
    return new MyPromise(resolve=>resolve(data));
  }
  // 不解釋
  static reject (err) {
    return new MyPromise((resolve, reject)=>{reject(err)});
  }

  // 測試方法
  var resolvePromise =  MyPromise.resolve(111);

  resolvePromise.then(data=>console.log(data));

  var rejectPromise =  MyPromise.reject('這個錯了');

  rejectPromise.catch(data=>console.log(data));

  new MyPromise();

  var errPromise = new MyPromise(()=>{throw new Error("我錯了")});
  errPromise.catch(data=>console.log(data.message));
  • thenable 對象 + 全局錯誤監聽

    thenable 對象是啥?就是有個屬性爲 then 方法的對象,then 方法裏有兩個參數,resolve、reject 至於 resolve 和 reject 的做用,就不贅述啦 好像仍是打了不少字

    全局錯誤監聽,監聽分爲兩種(書上的說法是): 一個觸發是當前事件循環結束前沒有catch 當前錯誤 Promise --- unhandledRejection;一個觸發是當前事件循環後,當 Promise 被拒絕,而且沒有 catch 程序,就會被觸發 --- rejectionHandled。通過 node 環境下測試(在 Chrome 控制檯測試好像不管如何都不會被觸發)感受是 rejectionHandled 觸發實在新的時間循環添加 catch 程序後纔會被觸發,大體流程圖以下。

    流程圖

    let rejected;
    
    process.on('unhandledRejection',function(event){
      console.log('onunhandledrejection');
    })
    
    process.on('rejectionHandled',function(event){
      console.log('onrejectionhandled');
    })
    
    rejected = Promise.reject(new Error('xx'))
    
    eventLoopEndRun(()=>{
      console.log(123);
      rejected.catch(err=>{
        console.log(err.message)
      })
      rejected.catch(err=>{
        console.log(err.message)
      })
    })

    分析一波:

    1. 在 reject 階段進行訂閱 unhanlderReject 事件;
    2. catch 函數中移除當前 PromiseunhandledRejection 事件的訂閱,執行傳入 catch 前發佈當前 PromiserejectionHandled 事件。
    3. 當前事件循環結束,咱們須要優先對 unhanlderReject 事件進行發佈,因此咱們須要調整eventLoopEndRun 函數;當Promise沒有 catch 程序,且沒有全局沒有 unhanlderReject 監聽,咱們就要拋出相應的錯誤。
    4. 咱們須要自定義這個 訂閱發佈者,而後能經過當前 Promise 使得事件觸發綁定相應的回調。
    5. 這個發佈訂閱者具備備的功能有: 一、新增監聽回調;二、訂閱和取消訂閱;三、相應的事件發佈後,將對應 map 中 Promise 修改狀態。

因而乎代碼以下:

// PromiseSubscribePublish.js
  const UNHANDLEDREJECTION = 'UNHANDLEDREJECTION'; // 當前事件循環,無 catch 函數狀態;
  const REJECTIONHANDLED = 'REJECTIONHANDLED'; // 事件循環後,無 catch 函數狀態;

  class PromiseSubscribePublish{

    constructor(){
      this.subscribeUnhandler = new Map();
      this.subscribeHandler = new Map();
      this.errFuc = {}
    }

    // 監聽事件綁定
    bindLisener (type, cb){
      console.log(type.toUpperCase(), UNHANDLEDREJECTION)
      if(type.toUpperCase() !== UNHANDLEDREJECTION && type.toUpperCase() !== REJECTIONHANDLED) throw Error('type toUpperCase must be UNHANDLEDREJECTION or REJECTIONHANDLED');
      if(Object.prototype.toString.call(cb) !== "[object Function]") throw Error('callback is not function');
      this.errFuc[type.toUpperCase()] = cb;
    }

    subscribe(promise, err){
      // 訂閱一波,以當前 Promise 爲 key,err 爲參數,加入 unhandler map 中
      this.subscribeUnhandler.set(promise, err)
    }

    quitSubscribe(promise){
      this.subscribeUnhandler.delete(promise);
    }

    publish (type, promise) {
      
      let changgeStateFuc; // 定義當前狀態變換操做
      const errFuc = this.errFuc[type]; // 當前綁定的監聽函數


      
      if(type === UNHANDLEDREJECTION){
        // 沒有訂閱事件的 promise 則啥也不幹
        if (!this.subscribeUnhandler.size) return;
        // 根據當前事件類型,選擇處理函數
        changgeStateFuc = (err, promise)=>{
          this.subscribeHandler.set(promise);
          this.subscribeUnhandler.delete(promise, err);
        }
        // 不論如何當前時間循環下的等待隊列狀態所有須要變動
        if(errFuc){
          this.subscribeUnhandler.forEach((err, promise)=>{
            errFuc(err, promise)
            changgeStateFuc(err, promise)
          })
        } else {
          this.subscribeUnhandler.forEach((err, promise)=>{
            changgeStateFuc(err, promise)
          })
          console.error('Uncaught (in promise)', err);
        }

      } else {
        // 若是該 promise 沒有進行訂閱
        if(!this.subscribeHandler.has(promise)) return;
        // 哪一個 promise 發佈 catch 函數,就根據當前 Promise 執行相應方法,並將其從 Handler 訂閱者裏刪除
        
        errFuc && errFuc(promise);
        this.subscribeHandler.delete(promise);

      } 

    }
  }

  // 定義一些靜態成員變量 默認不可寫
  Object.defineProperties(PromiseSubscribePublish, {
    [UNHANDLEDREJECTION]:{
      value: UNHANDLEDREJECTION
    },
    [REJECTIONHANDLED]:{
      value: REJECTIONHANDLED
    }
  })

  module.exports = PromiseSubscribePublish;

  // MyPromise.js
  // ..
  const PromiseSubscribePublish = require('./PromiseSubscribePublish');

  const promiseSubscribePublish = new PromiseSubscribePublish();

  // 事件循環最後執行
  const eventLoopEndRun = (()=>{
    let unhandledPub;
    let timer;
    const queueHandler = [];
    // 激活事件循環最後執行
    const activateRun = ()=>{
      // 截流
      timer && clearTimeout(timer);
      timer = setTimeout(()=>{
        unhandledPub && unhandledPub();
        let handler = queueHandler.shift();
        while(handler){
          handler();
          handler = queueHandler.shift();
        }
      },0);
    }
    
    // 設置 unhanldedReject 優先級最高 , 直接加入隊列
    return (handler,immediate)=> {
      immediate ? unhandledPub = handler : queueHandler.push(handler);
      activateRun();
    }
  })()
  
  //...
  reject (err) {
    this.changeStateHandler && this.changeStateHandler(REJECTED, err);
    promiseSubscribePublish.subscribe(this, err);
    // 存在 reject ,事件循環結束髮布 UNHANDLEDREJECTION
    eventLoopEndRun(()=>
      promiseSubscribePublish.publish(PromiseSubscribePublish.UNHANDLEDREJECTION, this),
      true
    );
  }

  //...

  static unhandledRejectionLisener(cb){
    promiseSubscribePublish.bindLisener(PromiseSubscribePublish.UNHANDLEDREJECTION ,cb)
  }

  static rejectionHandledLisener(cb){
    promiseSubscribePublish.bindLisener(PromiseSubscribePublish.REJECTIONHANDLED ,cb)
  }

  // ...
  catch(catchHandler){
    
    const currentState = this.getPromiseState();
    const promiseData = this.getPromiseValue();

    // 取消當前事件循環下 reject 狀態未 catch 事件訂閱;
    promiseSubscribePublish.quitSubscribe(this);
    
    if (currentState === REJECTED) {
      
      eventLoopEndRun(()=>{
        // 發佈 catch 處理
        promiseSubscribePublish.publish(PromiseSubscribePublish.REJECTIONHANDLED, this);
        catchHandler(promiseData);
      });

    }
    else if (currentState === PENDDING) this.catchQueue.push(catchHandler);
  }


  // 測試代碼

  MyPromise.unhandledRejectionLisener((err,promise)=>{
    console.log(err, promise);
  }) 
  MyPromise.rejectionHandledLisener((err,promise)=>{
    console.log(err, promise);
  }) 
  var myPromise = MyPromise.reject(11);
  // myPromise.catch(()=>{console.log('catch')});
  setTimeout(()=>{
    myPromise.catch(()=>{console.log('catch')});
  },1000)
  • 串聯 Promise 以及 Promise 鏈返回值

    看到鏈式,首先想到的是 jquery 調用。jquery 返回的是 jquery 對象本體。而 Promise 根據狀態判斷:

    • 當是操做成功狀態時,調用 catch 會返回和當前 Promise[[PromiseStatus]][[PromiseValues]] 狀態相同新構建的 Promise;調用 then 方法時,返回和當前 Promise[[PromiseStatus]] 相同的,[[PromiseValues]] 值爲 then 方法返回值的 新構建的 Promise
    • 當是捕獲錯誤狀態時,調用 then 會返回和當前 Promise[[PromiseStatus]][[PromiseValues]] 狀態相同新構建的 Promise;調用 catch 方法時, 返回操做成功的新構建的 Promise[[PromiseValues]] 值爲 catch 方法返回值;
    • 當執行 catch 或 then 方法體內有報錯,直接返回一個新構建捕獲錯誤的 Promise[[PromiseValues]] 爲那個錯誤;
    • 若是 Promise 中有一環出現錯誤,而鏈中沒有 catch 方法,則拋出錯誤,不然把鏈上的全部 Promise 都從 unhandledRejuect 訂閱中去除。
    • 由於 then 和 catch 回調方法是當前事件循環結束時才執行,而 catch 去除 Promise 鏈上 unhandledRejuect 訂閱是當前事件循環,若是鏈上有方法報錯,unhandledRejuect 訂閱會再次發生,這樣會形成哪怕當前報錯 Promise 後有 catch,也會拋出錯誤,所以須要給當前 Promise 加一個屬性,以標誌鏈後有 catch,使得其不訂閱 unhandledRejuect 事件。

分析一波:

1. 要在實例方法中,建立另外一個當前類的實例時,必須用到當前類的構造函數。當我們的類被繼承出一個派生類,我們但願返回的是那個派生類,因而不能直接 new MyPromise 去建立,而要使用一個 Symbol.species
2. 新建 *Promise* 和以前的 *Promise* 存在關聯,因此當前 *Promise* 的狀態決定新 *Promise* 狀態,構建新 *Promise* 的過程當中當前 *Promise* 的捕獲函數不能將其訂閱從 unhandledReject 中移除,因此須要一個標誌位來標識 then 函數屬性。
3. *Promise* 鏈上若是出現 catch 函數,鏈上 catch 函數以前的全部 *Promise* 都將從訂閱 unhandledReject Map 中移除,所以 *Promise* 須要記錄鏈上的上一級 *Promise*;
4. *Promise* then 或 catch 方法體內報錯將構建一個捕獲錯誤狀態的 *Promise*,所以須要一個函數去捕獲可能發生的錯誤;
//... MyPromise.js


  const runFucMaybeError = handler => {
    try {
      return handler();
    } catch(err) {
      return {
        iserror: FUCERROR,
        err
      };
    }
  }

  const clearLinksSubscribe = linkPrePromise=>{
    while(linkPrePromise && !linkPrePromise.hascatch){
      linkPrePromise.hascatch = true;
      promiseSubscribePublish.quitSubscribe(linkPrePromise);
      linkPrePromise = linkPrePromise.linkPrePromise;
    }
  }
  // 不解釋
  then(thenHandler, quitReturn){
    
    const currentState = this.getPromiseState();
    const promiseData = this.getPromiseValue();
    let nextPromiseData;
    if (currentState === FULFILLED) eventLoopEndRun(()=>{
      nextPromiseData = runFucMaybeError(()=>thenHandler(promiseData))
    });
    else if (currentState === PENDDING) this.thenQueue.push(data=>{
      nextPromiseData = runFucMaybeError(()=>thenHandler(data))
    });

    if(!quitReturn){
      const nextPromise = new this.constructor[Symbol.species]((resolve,reject)=>{
        
        this.catch(err=>{
          reject(err);
        }, true);
        // 根據隊列原則,執行確定在當前 then 後,保證能正確拿到前一個 Promise 的返回值
        this.then(()=>{
          nextPromiseData && nextPromiseData.iserror === FUCERROR 
            ? reject(nextPromiseData.err) 
              : resolve(nextPromiseData)
        }, true)
      })
      nextPromise.linkPrePromise = this;
      return nextPromise;
    };

  }

  catch(catchHandler, quitReturn){
    
    const currentState = this.getPromiseState();
    const promiseData = this.getPromiseValue();
    let nextPromiseData;
    // 取消當前事件循環下 reject 狀態未 catch 事件訂閱;
    // 當是實例內部調用時,不能將當前 Promise 從 unhandledReject 隊列中移除;
    // 不然順着生成鏈依次將 Promise 移除;
    if(!quitReturn)clearLinksSubscribe(this)
    if (currentState === REJECTED) {
      
      eventLoopEndRun(()=>{
        // 發佈 catch 處理
        promiseSubscribePublish.publish(PromiseSubscribePublish.REJECTIONHANDLED, this);
        nextPromiseData = runFucMaybeError(()=>catchHandler(promiseData));
      });

    }
    else if (currentState === PENDDING) this.catchQueue.push(data=>{
      nextPromiseData = runFucMaybeError(()=>{catchHandler(data)})
    });

    if(!quitReturn){
      
      const nextPromise = new this.constructor[Symbol.species]((resolve,reject)=>{
        // 根據隊列原則,執行確定在當前 then 後,保證能正確拿到報錯的 Promise 的返回值
        this.catch(()=>{
          nextPromiseData && nextPromiseData.iserror === FUCERROR 
          ? reject(nextPromiseData.err) 
            : resolve(nextPromiseData)
        }, true);
        this.then(data=>resolve(data), true)
      })
      nextPromise.linkPrePromise = this;
      return nextPromise;
    }

  }

  // 測試代碼
  const test1 = new MyPromise((resolve,reject)=>{
    setTimeout(()=>{
      resolve('2s 後輸出了我');
    }, 2000)
  });


  test1.then(data=>{
    console.log(data);
    return '你好'
  }).then(data=>{
    console.log(data);
    return '很差'
  }).then(data=>{
    console.log(data);
  });

  test1.catch(err=>console.log(err)).then(data=>{
    console.log(data);
    return 'gggg'
  }).then(data=>{
    console.log(data);
  });

  const test2 = new MyPromise((resolve,reject)=>{
    throw new Error('xx');
  })

  test2.then(data=>console.log(data)).catch(err=>console.log(err));

  test2.catch(err=>console.log(err)).then(data=>{
    console.log(data);
    return '你好'
  }).then(data=>{
    console.log(data);
    return '很差'
  }).then(data=>{
    console.log(data);
  });
  var a = MyPromise.resolve(1);
  var b = a.then(data=>{throw new Error('11')}).catch(err=>{console.log(err.message)})
  • Promise.all + Promise.race;

    Promise.all 有以下特性: 一、接收一個具備[Symbol.iterator]函數的數據, 返回一個 Promise,該 Promise 成功操做,then 方法傳入一個數組,數組數據位置和迭代器迭代返回的順序相關聯,該 Promise 捕獲錯誤 catch 裏的傳入捕獲的錯誤; 二、 迭代器遍歷結果若是是 Promise , 則將其 PromiseValue 做爲值,插入傳入數組對應的位置,當遍歷結果不是 Promise 直接插入數組對應位置,當遇到捕獲錯誤,或者 Promise 出現錯誤時直接將狀態轉變爲 rejected 狀態 ,從 catch 拿到相應錯誤的值;總結就是有錯立刻拋,要不等全部數據處理完才改變狀態;

    Promise.race 就不贅述:記住幾點,傳入參數要求和 .all 相同,數據處理方式是,先到先得,率先處理完的數據直接修改狀態。

    在分析一波以前,調整幾個以前的沒有考慮到的問題:

    1. 將狀態改變函數覆蓋操做移至 resolve 和 reject 函數中。
    2. reject 方法體執行全都由是否能改變狀態決定。
    3. reject 新增一個參數,表示不訂閱 unhandledReject 事件,由於 then 方法也會生成新的 Promise,而 then 鏈前有捕獲異常狀態的 Promise 會形成重複報錯,catch 無所謂,由於自己會Promise 鏈隊列。
// 開頭的 '-' 標示移除,'+' 表示新增
  // ... changeStateHandler 方法
  -  this.changeStateHandler = null;

  resolve (data) {
    if(this.changeStateHandler){
      this.changeStateHandler(FULFILLED, data);
      // 保持狀態只能改變一次
      this.changeStateHandler = null;
    }
  }

  reject (err, noSubscribe) {
    if(this.changeStateHandler){ 
      this.changeStateHandler(REJECTED, err);
      !noSubscribe && !this.hascatch && promiseSubscribePublish.subscribe(this, err);
      // 存在 reject ,事件循環結束髮布 UNHANDLEDREJECTION
      eventLoopEndRun(()=>
        promiseSubscribePublish.publish(PromiseSubscribePublish.UNHANDLEDREJECTION, this),
        true
      );
      // 保持狀態只能改變一次
      this.changeStateHandler = null;
    }
  }

  // then 方法
  - this.catch(err=>{
    reject(err)
  }, true);
  
  + this.catch(err=>reject(err, true), true);

接下來開始分析一波:

  1. 首先我們的判斷,傳入的是否具備 Symbol.iterator,沒有就直接拋錯(Promise 狀態會直接變爲 reject,就不往下說了);
  2. 由於我們定義的 MyPromise 因此判斷類型應該是 MyPromise,若是想要經過 Object.prototype.toString.call 去判斷,我們須要給我們的類加一個 tag
  3. .all 處理完一波數據插入結果值對應的位置,判斷是否數據徹底處理完,若是所有處理完才改變狀態。.race 處理完那個直接改變狀態,忽略後面、忽略後面、忽略後面(重要的事情嗶嗶3次)。
  4. 兩邊若是有傳入的 Promise 狀態出現捕獲異常,返回的 Promise 狀態即變爲異常,catch 獲得的值即爲傳入 Promise 異常的那個異常 繞死你
  5. 由於是靜態方法因此不能用 Symbol.species 構建實例。
// MyPromise.js 最後頭
  MyPromise.prototype[Symbol.toStringTag] = "MyPromise";


  static all (promiseArr){
    
    
    
    // 由於是靜態方法 沒法獲取 this 因此不能使用實例內部方法構建方式去構建新對象
    return new MyPromise((resolve,reject)=>{
      const iterator = isIterator(promiseArr);
      
      if(typeof iterator === 'string'){
        console.error(iterator);
        throw new Error(iterator);
      }

      let data = iterator.next();
      const result = [];
      let index = -1; // Promise 應存放返回數組的位置;
      let waitPromiseNum = 0; // 統計未完成的 Promise;
      
      let checkAllEnd = () => {
        return waitPromiseNum === 0;
      }

      while (data) {
        if(data.done) break;
        index ++;
        if(Object.prototype.toString.call(data.value) !== "[object MyPromise]"){
          result[index] = data.value;
        } else {

          (index=>{
            const promise = data.value; 
            waitPromiseNum++;
            promise.then(data=>{
              result[index] = data;
              waitPromiseNum--;
              // 看是否 Promise 所有完成
              if(checkAllEnd())resolve(result);
            }).catch(data=>reject(data));
          })(index)

        }
        data = iterator.next();
      }

      if(checkAllEnd())resolve(result);
    })
  }

  static race (promiseArr){
    
    // 由於是靜態方法 沒法獲取 this 因此不能使用實例內部方法構建方式去構建新對象
    return new MyPromise((resolve,reject)=>{
      const iterator = isIterator(promiseArr);

      if(typeof iterator === 'string'){
        console.error(iterator);
        throw new Error(iterator);
      }

      let data = iterator.next();
      while (data) {
        if(data.done) break;
        if(Object.prototype.toString.call(data.value) !== "[object MyPromise]"){
          return resolve(data.value);
        } else {
          data.value
            .then(data=>resolve(data))
            .catch(data=>reject(data));
        }
        data = iterator.next();
      }

    })
  }

  // 測試方法

  MyPromise.all(
    [
      MyPromise.resolve(1),
      new MyPromise(resolve=>setTimeout(()=>resolve(2), 1000)),
      MyPromise.resolve(3)
    ]).then(data=>{console.log(data)});

  MyPromise.all([
    1,
    new MyPromise(resolve=>setTimeout(()=>resolve(2), 1000)),
    MyPromise.resolve(3)
    ]).then(data=>{console.log(data)});

  MyPromise.all([
    MyPromise.resolve(1),
    new MyPromise(resolve=>setTimeout(()=>resolve(2), 1000)),
    MyPromise.reject(3)
    ]).then(data=>{console.log(data)});


  MyPromise.race([
    MyPromise.resolve(1),
    new MyPromise(resolve=>setTimeout(()=>resolve(2), 1000)),
    MyPromise.resolve(3)
    ]).then(data=>{console.log(data)});

  MyPromise.race([
    1,
    new MyPromise(resolve=>setTimeout(()=>resolve(2), 1000)),
    MyPromise.resolve(3)
    ]).then(data=>{console.log(data)});
    
  MyPromise.race([
    MyPromise.resolve(1),
    new MyPromise(resolve=>setTimeout(()=>resolve(2), 1000)),
    MyPromise.reject(3)
    ]).then(data=>{console.log(data)});

結束

若是發現過程遇到什麼問題,歡迎及時提出。
相關文章
相關標籤/搜索