用 class 寫法完整實現(xiàn)一個 Promise

1.前言

本文分析 Promise 特性的了解数初,完整實現(xiàn)了 Promise 所有功能次酌。沒有參考原生 Promise 的寫法秀存,自己根據(jù)思路一步一步完成以及描述羔杨,每個構建模塊由:1性誉、Promise 特性描述窿吩;2、實現(xiàn)特性的完整思路(分析一波) 3错览、項目代碼纫雁;4、功能測試代碼 幾個部分組成倾哺。大致用到的知識有: 1轧邪、變量私有化;2羞海、訂閱發(fā)布模式忌愚;3、eventloop 理解却邓;4硕糊、Promise特性;5腊徙、class 特性简十;6、對象類型的判定... 算了不寫了強行塞這么多我也是夠拼的

你可以點我看源碼撬腾、點我看原文地址

2.Promise 特征分析

  • Promise 有三種狀態(tài): pending(執(zhí)行中)螟蝙、 fulfilled(成功執(zhí)行)、settled(異常捕獲);
  • Promise 可以通過 new 關鍵字創(chuàng)建一個 未完成的 Promise;
  • Promise 可以直接通過 Promise.resolve 創(chuàng)建一個成功完成的 Promise 對象;
  • Promise 可以直接通過 Promise.reject 創(chuàng)建一個異常狀態(tài)的 Promise 對象;
  • 通過 new 關鍵字創(chuàng)建的 Promise 方法里如果出現(xiàn)錯誤民傻,會被 Promise 的 reject 捕獲;
  • Promise.resolve / Promise.reject 接收 thenable 對象和 Promise 對象的處理方式;
  • 當沒有錯誤處理時的胰默,全局的 Promise 拒絕處理;
  • 串聯(lián) Promise 以及 Promise 鏈返回值;
  • Promise.all Promise.race;

3.Promise 的實現(xiàn)

  • 狀態(tài)碼私有化

    開始之前討論一波 class 私有屬性的實現(xiàn)场斑,個人想到的方案如下:

    1.通過閉包,將變量存放在 construct 方法里;弊端初坠,所有的其他的對象方法必須在 construct 內定義(NO)和簸。

    2.通過在定義 Promise 的環(huán)境下定義一個 Map彭雾,根據(jù)當前對象索引去獲取相應的私有值碟刺;弊端,因為 Map 的 key 是強引用薯酝,當定義的 Promise 不用時也不會被內存回收(NO)半沽;

    3.通過在定義 Promise 的環(huán)境下定義一個 WeakMap,根據(jù)當前對象索引去獲取相應的私有值吴菠; 優(yōu)勢者填,木有以上兩種劣勢(不寫點什么感覺難受);

    說了這么多那么咱們要用第三種方法嗎做葵?NO占哟,原生 [[PromiseState]] 是一個內部屬性,不暴露在 Promise 上酿矢,但是通過瀏覽器的控制臺可以看到榨乎,用第三種方式模仿并不能直觀的在控制臺看到,所以我決定還是不要作為私有變量出現(xiàn)瘫筐,但是把枚舉特性干掉了 假裝他是私有變量 心里好過一點 因此你就能看到下面的代碼;


const PENDDING = 'pendding';// 等待狀態(tài)
const FULFILLED = 'resolved';// 成功操作狀態(tài)
const REJECTED = 'rejected';// 捕獲錯誤狀態(tài)

class MyPromise{
  
  constructor(handler){
    // 數(shù)據(jù)初始化
    this.init();
  }
  
  // 數(shù)據(jù)初始化
  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
      }
    })
  }
  // 獲取當前狀態(tài)
  getPromiseState (){
    return this['[[PromiseState]]'];
  }
  // 設置當前狀態(tài)
  setPromiseState (state) {
    Object.defineProperty(this, '[[PromiseState]]', {
      value: state,
      writable: false
    })
  }

  // 獲取當前值
  getPromiseValue (){
    return this['[[PromiseValue]]'];
  }
  // 設置當前值
  setPromiseValue (val) {
    Object.defineProperty(this, '[[PromiseValue]]', {
      value: val
    })
  }
}

  • 創(chuàng)建一個未完成狀態(tài)的Promise

    函數(shù)調用過程分析:

    1. 使用者通過 new 關鍵字傳入一個方法蜜暑;
    2. 方法有兩個參數(shù) resolvereject 兩個方法
    3. 當傳入的方法調用 resolve 時,狀態(tài)變?yōu)?fulfilled策肝,有且只有接收一次 resolve 里的方法里的值作為 [[PromiseValue]]肛捍,供該 Promise 對象下的 then 方法使用;
    4. 當傳入的方法調用 reject 時之众,狀態(tài)變?yōu)?rejected拙毫,有且只有接收一次 reject 里的方法里的值作為 [[PromiseValue]],供該 Promise 對象下的 catch 方法使用棺禾;

    代碼思路:

    1. 首先傳入的函數(shù)應該在 construct 方法里進行調用缀蹄;

    2. 因具備一個存放待執(zhí)行成功操作方法的隊列,一個存放捕獲異常方法的隊列帘睦。

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

      1袍患、判斷當前狀態(tài)是否是等待狀態(tài),如果不是則啥也不干竣付,如果是走第二步

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

      3、將 [[PromiseValue]] 賦值為方法傳遞進來的參數(shù)古胆;

      4肆良、成功操作方法的隊列在 eventloop 結束后依次調用然后清空筛璧,捕獲異常方法的隊列清空;

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

    5. then 方法:

      1惹恃、 判斷當前狀態(tài)是否為等待夭谤,是等待進行第 2 步,否則進行第 3 步巫糙;

      2朗儒、 加入成功操作方法隊列;

      3参淹、 當前eventloop 結束異步調用醉锄;

    6. catch 方法不贅述

    ps: 注①因為無法將任務插入 microtask 中,就用 eventloop結束作為替代浙值;

  // 事件循環(huán)最后執(zhí)行
  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 = []
    }

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

      this.setPromiseState(currentState);
      this.setPromiseValue(data);
      setImmediate(()=>{this.clearQueue(currentState)});
      
      // 保持狀態(tài)只能改變一次
      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("我是最早的");

  • 創(chuàng)建一個完成狀態(tài)的Promise

    通過 Promise.resolve() 創(chuàng)建一個成功操作的 Promise 對象开呐; Promise.reject() 創(chuàng)建一個捕獲錯誤的 Promise 對象烟勋,new 關鍵字傳入的方法體有報錯,會直接被 reject 捕獲筐付;

    分析一波:

    1. 能直接調用的方法卵惦,妥妥應該的是一個靜態(tài)方法;

    2. 調用之后要生成一個新的 Promise 對象家妆;

    3. 所以咱們就要分兩步走 1鸵荠,創(chuàng)建一個 Promise 對象,然后調用其 resolve 方法.

    4. 因為實例化的對象不能獲取寄幾的 static 方法

    5. 通過 try+catch 捕獲 handler 異常伤极,并通過 reject 進行拋出蛹找;


  // ...
  // construct 方法新增一個類型,當 new 關鍵字進來傳遞的不是一個函數(shù)哨坪,咱們同樣在 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 對象 + 全局錯誤監(jiān)聽

    thenable 對象是啥当编?就是有個屬性為 then 方法的對象届慈,then 方法里有兩個參數(shù),resolve忿偷、reject 至于 resolve 和 reject 的作用金顿,就不贅述啦 好像還是打了很多字

    全局錯誤監(jiān)聽鲤桥,監(jiān)聽分為兩種(書上的說法是): 一個觸發(fā)是當前事件循環(huán)結束前沒有catch 當前錯誤 Promise --- unhandledRejection揍拆;一個觸發(fā)是當前事件循環(huán)后,當 Promise 被拒絕茶凳,并且沒有 catch 程序嫂拴,就會被觸發(fā) --- rejectionHandled播揪。經過 node 環(huán)境下測試(在 Chrome 控制臺測試好像無論如何都不會被觸發(fā))感覺是 rejectionHandled 觸發(fā)實在新的時間循環(huán)添加 catch 程序后才會被觸發(fā),大致流程圖如下筒狠。

    流程圖
    
    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 函數(shù)中移除當前 PromiseunhandledRejection 事件的訂閱,執(zhí)行傳入 catch 前發(fā)布當前 PromiserejectionHandled 事件辩恼。

    3. 當前事件循環(huán)結束雇庙,我們需要優(yōu)先對 unhanlderReject 事件進行發(fā)布,所以我們需要調整eventLoopEndRun 函數(shù)运挫;當Promise沒有 catch 程序,且沒有全局沒有 unhanlderReject 監(jiān)聽状共,我們就要拋出相應的錯誤套耕。

    4. 我們需要自定義這個 訂閱發(fā)布者谁帕,然后能通過當前 Promise 使得事件觸發(fā)綁定相應的回調。

    5. 這個發(fā)布訂閱者具有備的功能有: 1冯袍、新增監(jiān)聽回調匈挖;2、訂閱和取消訂閱康愤;3儡循、相應的事件發(fā)布后,將對應 map 中 Promise 修改狀態(tài)征冷。

于是乎代碼如下:

  // PromiseSubscribePublish.js
  const UNHANDLEDREJECTION = 'UNHANDLEDREJECTION'; // 當前事件循環(huán)择膝,無 catch 函數(shù)狀態(tài);
  const REJECTIONHANDLED = 'REJECTIONHANDLED'; // 事件循環(huán)后检激,無 catch 函數(shù)狀態(tài)肴捉;

  class PromiseSubscribePublish{

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

    // 監(jiān)聽事件綁定
    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 為參數(shù),加入 unhandler map 中
      this.subscribeUnhandler.set(promise, err)
    }

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

    publish (type, promise) {
      
      let changgeStateFuc; // 定義當前狀態(tài)變換操作
      const errFuc = this.errFuc[type]; // 當前綁定的監(jiān)聽函數(shù)


      
      if(type === UNHANDLEDREJECTION){
        // 沒有訂閱事件的 promise 則啥也不干
        if (!this.subscribeUnhandler.size) return;
        // 根據(jù)當前事件類型齿穗,選擇處理函數(shù)
        changgeStateFuc = (err, promise)=>{
          this.subscribeHandler.set(promise);
          this.subscribeUnhandler.delete(promise, err);
        }
        // 不論如何當前時間循環(huán)下的等待隊列狀態(tài)全部需要變更
        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 發(fā)布 catch 函數(shù),就根據(jù)當前 Promise 執(zhí)行相應方法饺律,并將其從 Handler 訂閱者里刪除
        
        errFuc && errFuc(promise);
        this.subscribeHandler.delete(promise);

      } 

    }
  }

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

  module.exports = PromiseSubscribePublish;

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

  const promiseSubscribePublish = new PromiseSubscribePublish();

  // 事件循環(huán)最后執(zhí)行
  const eventLoopEndRun = (()=>{
    let unhandledPub;
    let timer;
    const queueHandler = [];
    // 激活事件循環(huán)最后執(zhí)行
    const activateRun = ()=>{
      // 截流
      timer && clearTimeout(timer);
      timer = setTimeout(()=>{
        unhandledPub && unhandledPub();
        let handler = queueHandler.shift();
        while(handler){
          handler();
          handler = queueHandler.shift();
        }
      },0);
    }
    
    // 設置 unhanldedReject 優(yōu)先級最高 窃页, 直接加入隊列
    return (handler,immediate)=> {
      immediate ? unhandledPub = handler : queueHandler.push(handler);
      activateRun();
    }
  })()
  
  //...
  reject (err) {
    this.changeStateHandler && this.changeStateHandler(REJECTED, err);
    promiseSubscribePublish.subscribe(this, err);
    // 存在 reject ,事件循環(huán)結束發(fā)布 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();

    // 取消當前事件循環(huán)下 reject 狀態(tài)未 catch 事件訂閱;
    promiseSubscribePublish.quitSubscribe(this);
    
    if (currentState === REJECTED) {
      
      eventLoopEndRun(()=>{
        // 發(fā)布 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)


  • 串聯(lián) Promise 以及 Promise 鏈返回值

    看到鏈式复濒,首先想到的是 jquery 調用脖卖。jquery 返回的是 jquery 對象本體。而 Promise 根據(jù)狀態(tài)判斷:

    • 當是操作成功狀態(tài)時巧颈,調用 catch 會返回和當前 Promise[[PromiseStatus]][[PromiseValues]] 狀態(tài)相同新構建的 Promise畦木;調用 then 方法時,返回和當前 Promise[[PromiseStatus]] 相同的洛二,[[PromiseValues]] 值為 then 方法返回值的 新構建的 Promise馋劈;
    • 當是捕獲錯誤狀態(tài)時攻锰,調用 then 會返回和當前 Promise[[PromiseStatus]][[PromiseValues]] 狀態(tài)相同新構建的 Promise;調用 catch 方法時妓雾, 返回操作成功的新構建的 Promise 娶吞,[[PromiseValues]] 值為 catch 方法返回值;
    • 當執(zhí)行 catch 或 then 方法體內有報錯械姻,直接返回一個新構建捕獲錯誤的 Promise 妒蛇,[[PromiseValues]] 為那個錯誤;
    • 如果 Promise 中有一環(huán)出現(xiàn)錯誤楷拳,而鏈中沒有 catch 方法绣夺,則拋出錯誤,否則把鏈上的所有 Promise 都從 unhandledRejuect 訂閱中去除欢揖。
    • 因為 then 和 catch 回調方法是當前事件循環(huán)結束時才執(zhí)行陶耍,而 catch 去除 Promise 鏈上 unhandledRejuect 訂閱是當前事件循環(huán),如果鏈上有方法報錯她混,unhandledRejuect 訂閱會再次發(fā)生烈钞,這樣會造成哪怕當前報錯 Promise 后有 catch,也會拋出錯誤坤按,因此需要給當前 Promise 加一個屬性毯欣,以標志鏈后有 catch,使得其不訂閱 unhandledRejuect 事件臭脓。

分析一波:
1. 要在實例方法中酗钞,創(chuàng)建另一個當前類的實例時,必須用到當前類的構造函數(shù)来累。當咱們的類被繼承出一個派生類砚作,咱們希望返回的是那個派生類,于是不能直接 new MyPromise 去創(chuàng)建,而要使用一個 Symbol.species
2. 新建 Promise 和之前的 Promise 存在關聯(lián),所以當前 Promise 的狀態(tài)決定新 Promise 狀態(tài)佃扼,構建新 Promise 的過程中當前 Promise 的捕獲函數(shù)不能將其訂閱從 unhandledReject 中移除偎巢,所以需要一個標志位來標識 then 函數(shù)屬性。
3. Promise 鏈上如果出現(xiàn) catch 函數(shù)兼耀,?鏈上 catch 函數(shù)之前的所有 Promise 都將從訂閱 unhandledReject Map 中移除压昼,因此 Promise 需要記錄鏈上的上一級 Promise
4. Promise then 或 catch 方法體內報錯將構建一個捕獲錯誤狀態(tài)的 Promise瘤运,因此需要一個函數(shù)去捕獲可能發(fā)生的錯誤窍霞;


  //... 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);
        // 根據(jù)隊列原則,執(zhí)行肯定在當前 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;
    // 取消當前事件循環(huán)下 reject 狀態(tài)未 catch 事件訂閱;
    // 當是實例內部調用時,不能將當前 Promise 從 unhandledReject 隊列中移除但金;
    // 否則順著生成鏈依次將 Promise 移除;
    if(!quitReturn)clearLinksSubscribe(this)
    if (currentState === REJECTED) {
      
      eventLoopEndRun(()=>{
        // 發(fā)布 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)=>{
        // 根據(jù)隊列原則郁季,執(zhí)行肯定在當前 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]函數(shù)的數(shù)據(jù), 返回一個 Promise似枕,該 Promise 成功操作盖淡,then 方法傳入一個數(shù)組,數(shù)組數(shù)據(jù)位置和迭代器迭代返回的順序相關聯(lián)凿歼,該 Promise 捕獲錯誤 catch 里的傳入捕獲的錯誤; 2褪迟、 迭代器遍歷結果如果是 Promise , 則將其 PromiseValue 作為值,插入傳入數(shù)組對應的位置答憔,當遍歷結果不是 Promise 直接插入數(shù)組對應位置味赃,當遇到捕獲錯誤,或者 Promise 出現(xiàn)錯誤時直接將狀態(tài)轉變?yōu)?rejected 狀態(tài) 虐拓,從 catch 拿到相應錯誤的值心俗;總結就是有錯馬上拋,要不等所有數(shù)據(jù)處理完才改變狀態(tài)侯嘀;

    Promise.race 就不贅述:記住幾點另凌,傳入?yún)?shù)要求和 .all 相同,數(shù)據(jù)處理方式是戒幔,先到先得,率先處理完的數(shù)據(jù)直接修改狀態(tài)土童。

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

    1. 將狀態(tài)改變函數(shù)覆蓋操作移至 resolve 和 reject 函數(shù)中。
    2. reject 方法體執(zhí)行全都由是否能改變狀態(tài)決定献汗。
    3. reject 新增一個參數(shù)敢订,表示不訂閱 unhandledReject 事件,因為 then 方法也會生成新的 Promise罢吃,而 then 鏈前有捕獲異常狀態(tài)的 Promise 會造成重復報錯楚午,catch 無所謂,因為本身會Promise 鏈隊列。
  // 開頭的 '-' 標示移除尿招,'+' 表示新增
  // ... changeStateHandler 方法
  -  this.changeStateHandler = null;

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

  reject (err, noSubscribe) {
    if(this.changeStateHandler){ 
      this.changeStateHandler(REJECTED, err);
      !noSubscribe && !this.hascatch && promiseSubscribePublish.subscribe(this, err);
      // 存在 reject 矾柜,事件循環(huán)結束發(fā)布 UNHANDLEDREJECTION
      eventLoopEndRun(()=>
        promiseSubscribePublish.publish(PromiseSubscribePublish.UNHANDLEDREJECTION, this),
        true
      );
      // 保持狀態(tài)只能改變一次
      this.changeStateHandler = null;
    }
  }

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

接下來開始分析一波:

  1. 首先咱們的判斷,傳入的是否具有 Symbol.iterator就谜,沒有就直接拋錯(Promise 狀態(tài)會直接變?yōu)?reject怪蔑,就不往下說了);

  2. 因為咱們定義的 MyPromise 所以判斷類型應該是 MyPromise丧荐,如果想要通過 Object.prototype.toString.call 去判斷缆瓣,咱們需要給咱們的類加一個 tag

  3. .all 處理完一波數(shù)據(jù)插入結果值對應的位置,判斷是否數(shù)據(jù)完全處理完虹统,如果全部處理完才改變狀態(tài)弓坞。.race 處理完那個直接改變狀態(tài)隧甚,忽略后面、忽略后面渡冻、忽略后面(重要的事情嗶嗶3次)呻逆。

  4. 兩邊如果有傳入的 Promise 狀態(tài)出現(xiàn)捕獲異常,返回的 Promise 狀態(tài)即變?yōu)楫惓#琧atch 得到的值即為傳入 Promise 異常的那個異常 繞死你菩帝。

  5. 因為是靜態(tài)方法所以不能用 Symbol.species 構建實例咖城。

  // MyPromise.js 最后頭
  MyPromise.prototype[Symbol.toStringTag] = "MyPromise";


  static all (promiseArr){
    
    
    
    // 因為是靜態(tài)方法 無法獲取 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 應存放返回數(shù)組的位置;
      let waitPromiseNum = 0; // 統(tǒng)計未完成的 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){
    
    // 因為是靜態(tài)方法 無法獲取 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)});

結束

如果發(fā)現(xiàn)過程遇到什么問題宜雀,歡迎及時提出。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末握础,一起剝皮案震驚了整個濱河市辐董,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌禀综,老刑警劉巖简烘,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異定枷,居然都是意外死亡孤澎,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門欠窒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來覆旭,“玉大人,你說我怎么就攤上這事岖妄⌒徒” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵荐虐,是天一觀的道長七兜。 經常有香客問我,道長福扬,這世上最難降的妖魔是什么腕铸? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮忧换,結果婚禮上恬惯,老公的妹妹穿的比我還像新娘。我一直安慰自己亚茬,他們只是感情好酪耳,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般碗暗。 火紅的嫁衣襯著肌膚如雪颈将。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天言疗,我揣著相機與錄音晴圾,去河邊找鬼。 笑死噪奄,一個胖子當著我的面吹牛死姚,可吹牛的內容都是我干的。 我是一名探鬼主播勤篮,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼都毒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了碰缔?” 一聲冷哼從身側響起账劲,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎金抡,沒想到半個月后瀑焦,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡梗肝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年榛瓮,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片统捶。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡榆芦,死狀恐怖,靈堂內的尸體忽然破棺而出喘鸟,到底是詐尸還是另有隱情,我是刑警寧澤驻右,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布什黑,位于F島的核電站,受9級特大地震影響堪夭,放射性物質發(fā)生泄漏愕把。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一森爽、第九天 我趴在偏房一處隱蔽的房頂上張望恨豁。 院中可真熱鬧,春花似錦爬迟、人聲如沸橘蜜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽计福。三九已至跌捆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間象颖,已是汗流浹背佩厚。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留说订,地道東北人抄瓦。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像陶冷,于是被迫代替她去往敵國和親钙姊。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內容

  • Promise 對象 Promise 的含義 Promise 是異步編程的一種解決方案埃叭,比傳統(tǒng)的解決方案——回調函...
    neromous閱讀 8,705評論 1 56
  • 本文適用的讀者 本文寫給有一定Promise使用經驗的人摸恍,如果你還沒有使用過Promise,這篇文章可能不適合你赤屋,...
    HZ充電大喵閱讀 7,305評論 6 19
  • 目錄:Promise 的含義基本用法Promise.prototype.then()Promise.prototy...
    BluesCurry閱讀 1,494評論 0 8
  • Promiese 簡單說就是一個容器立镶,里面保存著某個未來才會結束的事件(通常是一個異步操作)的結果,語法上說类早,Pr...
    雨飛飛雨閱讀 3,358評論 0 19
  • 張行烜媚媒,是張家年輕一輩里目前武術最高的一個,其余的都在三流初期涩僻,中期那缭召,很多甚至都到不了三流高手。 不過張家也不...
    白速閱讀 535評論 0 3