用 class 寫法完整實現一個 Promise

maut4x2oi3dl15m8nuil3v53p發表於2018-05-16

1.前言

本文分析 Promise 特性的瞭解,完整實現了 Promise 所有功能。沒有參考原生 Promise 的寫法,自己根據思路一步一步完成以及描述,每個構建模組由:1、Promise 特性描述;2、實現特性的完整思路(分析一波) 3、專案程式碼;4、功能測試程式碼 幾個部分組成。大致用到的知識有: 1、變數私有化;2、訂閱釋出模式;3、eventloop 理解;4、Promise特性;5、class 特性;6、物件型別的判定... 算了不寫了強行塞這麼多我也是夠拼的

你可以點我看原始碼點我看原文地址

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 私有屬性的實現,個人想到的方案如下:

    1.通過閉包,將變數存放在 construct 方法裡;弊端,所有的其他的物件方法必須在 construct 內定義(NO)。

    2.通過在定義 Promise 的環境下定義一個 Map,根據當前物件索引去獲取相應的私有值;弊端,因為 Map 的 key 是強引用,當定義的 Promise 不用時也不會被記憶體回收(NO);

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

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


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

    函式呼叫過程分析:

    1. 使用者通過 new 關鍵字傳入一個方法;
    2. 方法有兩個引數 resolvereject 兩個方法
    3. 當傳入的方法呼叫 resolve 時,狀態變為 fulfilled,有且只有接收一次 resolve 裡的方法裡的值作為 [[PromiseValue]],供該 Promise 物件下的 then 方法使用;
    4. 當傳入的方法呼叫 reject 時,狀態變為 rejected,有且只有接收一次 reject 裡的方法裡的值作為 [[PromiseValue]],供該 Promise 物件下的 catch 方法使用;

    程式碼思路:

    1. 首先傳入的函式應該在 construct 方法裡進行呼叫;

    2. 因具備一個存放待執行成功操作方法的佇列,一個存放捕獲異常方法的佇列。

    3. resolve 方法下處理的問題是:

      1、判斷當前狀態是否是等待狀態,如果不是則啥也不幹,如果是走第二步

      2、修改[[PromiseState]]為FULFILLED;

      3、將 [[PromiseValue]] 賦值為方法傳遞進來的引數;

      4、成功操作方法的佇列在 eventloop 結束後依次呼叫然後清空,捕獲異常方法的佇列清空;

    4. reject 方法基本就不贅述啦......

    5. then 方法:

      1、 判斷當前狀態是否為等待,是等待進行第 2 步,否則進行第 3 步;

      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. 這個釋出訂閱者具有備的功能有: 1、新增監聽回撥;2、訂閱和取消訂閱;3、相應的事件釋出後,將對應 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 有如下特性: 1、接收一個具有[Symbol.iterator]函式的資料, 返回一個 Promise,該 Promise 成功操作,then 方法傳入一個陣列,陣列資料位置和迭代器迭代返回的順序相關聯,該 Promise 捕獲錯誤 catch 裡的傳入捕獲的錯誤; 2、 迭代器遍歷結果如果是 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)});
複製程式碼

結束

如果發現過程遇到什麼問題,歡迎及時提出。

相關文章