特皮技術團隊:一年前端手寫Promise

從零手寫Promise

  • 在這個前端快速發展的時代,今天不努力,明天變垃圾,雖然這是一個表情包上看到的,相信你們必定都看過,說實在的,這話是粗,可是事實就是如此。
  • 言歸正傳咱們看看今天的內容把。
  • 上次咱們看了js的重點也是難點 「異步」,趁熱打鐵,今天看看從零手寫Promise

咱們先看看代碼
首先咱們先來回顧一下Promise的用法前端

new Promise((resolve,reject)=>{
    resolve('正確的')
}).then(value =>{
    console.log(value);
})
  • 能夠看的到在瀏覽器打印出了 "正確的"Promise的用法並不難,咱們只要知道 new Promise Promise傳入function(resolve,reject),接着在函數內作須要作的事情便可,等到須要的時候,對這個構造函數.then(res=>{})便可獲取對應的值

下面咱們就來分析一下Promise應該如何實現

  • 首先咱們應該定義一個Promise的結構
  • 本文覺得es5 的方式
(function (window) {
    function MyPromise(executor) {                                          // 1  定義MyPromise 構造函數
            
        function resolve(value) {                                           // 定義resolve                               
        }
        
        function reject(reason) {                                           // 定義reject  
        }

        MyPromise.prototype.then = function (onResolved,onRejected) {       // 定義then
        }

        MyPromise.prototype.catch = function (error) {                      // 定義catch
        }

        // 定義執行器的執行
        executor(resolve,reject);

    }
    
    window.MyPromise = MyPromise;                  // 2導出
    
})(window)
  • 接着能夠看看下面的執行
(function (window) {
    // Promise  
    function Mypromise(executor) {
        const self = this;                                  // 須要定義一下this指向
        self.status = 'pennding';                           // 初始化狀態
        self.data = undefined;                              // promise對象指定一個用於存儲結果數據
        self.callbacks = [];                                // 每一個元素的結構  { onResolved(){}, onRejected(){}}

        function resolve(value) {                           // resolve
            if (self.status !== 'pennding') {               // 由於promise 狀態只能修改一次
                return;
            }
            self.status = 'resolve';                        // 改變爲 resolve                  
            self.data = value;                              // 保存value的值                        
            if (self.callbacks.length > 0) {                // 若是有待執行的callback函數,當即執行回調函數onResolved
                setTimeout(() => {                          // 當前方案是爲了將任務掛在隊列中,製造異步
                    self.callbacks.forEach(callbacksObj => {
                        callbacksObj.onResolved(value)
                    })
                },0)
            }
        }

        function reject(reason) {                            //reject
            if (self.status !== 'pennding') {                // 由於promise 狀態只能修改一次
                return;
            }
            self.status = 'reject';                          // 改變爲 reject
            self.data = reason;                              // 保存reason的值                        
            if (self.callbacks.length > 0) {                 // 若是有待執行的callback函數,當即執行回調函數onRejected
                setTimeout(() => {
                    self.callbacks.forEach(callbacksObj => {
                        callbacksObj.onRejected(reason)
                    })
                },0)
            }
        }

        try {                                                 // 若是執行器拋出異常
            executor(resolve, reject);
        } catch (error) {
            reject(error)
        }
    }

    // Promise.then()
    Mypromise.prototype.then = function (onResolved, onRejected) {
        // 假設當前狀態仍是pennding 
        const self = this;
        self.callbacks.push({
            onResolved,
            onRejected
        })

    }

    // Promise.catch()
    Mypromise.prototype.carch = function (error) {

    }

    window.Mypromise = Mypromise;
})(window);
<body>
    <script src="./promise.js"></script>
    <script>

        const p = new Mypromise((resolve, reject) => {
            setTimeout(() => {           // 由於拆分 then還未處理,須要p.then 先執行
                resolve(1)
                console.log("我先")
            }, 100)
        })

        p.then(value => {
            console.log("onResolve()1", value)
        }, reason => {
            console.l("onReject()1", reason)
        })

        p.then(value => {
            console.log("onResolve()2", value)
        }, reason => {
            console.l("onReject()2", reason)
        })
    </script>
</body>

  • 能夠看的到執行結構是異步的,第一步成功了。

上面的代碼咱們來說解一下,

  • 一、首先是經過es5寫的,因此須要導出咱們使用了 閉包

  • 二、 建立Promise構造函數 並導出

  • 三、咱們使用promise時都知道往裏面傳入一個函數,這個函數帶有resolve,reject而且在函數裏面執行須要執行的操做,

因此這個接受的參數叫作執行器(executor)promise

  • 四、執行器裏執行的是兩個函數

  • 五、咱們都知道promise有三個狀態其中初始化時pendding

  • 六、根據resolve,reject改變狀態,改變值

  • 七、咱們都知道promise構造函數還有then方法和catch方法

  • 八、別忘了promise也能夠throw Error,因此執行器(executor)須要修改一下

  • 九、最後調用並執行看看結果
<body>
    <script src="./promise1.js"></script>
    <script>

        const p = new MyPromise((resolve, reject) => {
            setTimeout(() => {            // 由於拆分緣由then 還未作對應處理,此時不能改變狀態
                resolve(1)
                console.log("我先")
            }, 100)
        })

        p.then(value => {
            console.log("onResolve()1", value)
        }, reason => {
            console.l("onReject()1", reason)
        })

        p.then(value => {
            console.log("onResolve()2", value)
        }, reason => {
            console.l("onReject()2", reason)
        })
    </script>
</body>

  • 怎麼結果不對呢
  • 再來看看 由於直接執行的回調是同步執行的,須要將任務放入隊列中

  • 此時結果正確了。
  • 最後別忘了promise狀態只容許修改一次

  • 好的上面是簡易版的Promise並不包括then的實現。

接下來咱們在上面的基礎對then以及總體進行了一個升級

  • 代碼以下
(function (window) {
  const PENDDING = 'pendding';
  const FULFILLED = 'fulfilled';
  const REJECTED = 'rejected';

  function MyPromise(executor) {                                          // 定義MyPromises構造函數
    const self = this;
    self.status = PENDDING;
    self.data = undefined;
    self.callbacks = [];

    function resolve(value) {
      if (self.status === PENDDING) {
        self.status = FULFILLED;                                           // 改變MyPromise狀態
        self.data = value;                                                 // MyPromise 的值對應變化            
        setTimeout(() => {                                                 // 異步執行 
          self.callbacks.forEach(callbacksObj => {                         // 若是有待執行的callback函數,當即執行回調
            callbacksObj.onResolved(value)                              
          })
        })
      }
    }

    function reject(reason) {
      if (self.status === PENDDING) {
        self.status = REJECTED;
        self.data = reason;
        setTimeout(() => {
          self.callbacks.forEach(callbacksObj => {
            callbacksObj.onRejected(reason);
          });
        })
      }
    }

    try {                                                                   // MyPromise能夠拋出異常
      executor(resolve, reject);
    } catch (error) {
      reject(error)
    }
  }
  /* 
  MyPromise原型對象的then()
  指定成功和失敗回調
  返回一個新的回調函數
  // 返回的MyPromise結果由onResolved/onRejected的結果決定
  */
  MyPromise.prototype.then = function (onResolved, onRejected) { // 定義then      
    const self = this;
    // 指定回調函數的默認值(必須是函數)
    onResolved = typeof onResolved==='function' ?  onResolved : value => value;
    onRejected = typeof onRejected==='function' ?  onRejected : reason => {throw reason};

    return new MyPromise((resolve,reject)=>{                    // 返回一個新的MyPromise對象
      function handle(callback) {
          // 返回的MyPromise結果由onResolved/onRejected的結果決定
        // 一、拋出異常MyPromise結果爲失敗      reason爲結果
        // 二、返回的是MyPromise               MyPromise爲當前的結果
        // 三、返回的不是MyPromise             value爲結果

        // 須要經過捕獲獲取才能知道有沒有異常
        try{
          const result = callback(self.data)
          // 判斷是否是MyPromise
          if ( result instanceof MyPromise){
            // 只有then才知道結果
            result.then(value=>resolve(value),reason=>reject(reason))
          }else{
            resolve(result)
          }
        }catch(error){
          reject(error)                                          // 返回的結果爲reject(error)  上面第一點
        }
      }
    
      // 判斷當前的status
      if (self.status === FULFILLED){                           // 狀態爲 fulfilled
        setTimeout(()=>{                                        // 當即執行異步回調
          handle(onResolved);
        })                                                        
      } else if (self.status === REJECTED){                     // 狀態爲 rejected
        setTimeout(()=>{                                        // 當即執行異步回調
          handle(onRejected);
        })
      }else{                                                    // pendding將成功和失敗保存到callbacks裏緩存起來
        self.callbacks.push({
          onResolved(value){                                    //函數裏面調用回調函數 而且根據回調函數的結果改變MyPromise的結果
            handle(onResolved)                                  //爲何這裏沒有setTimeout,由於上面已經寫到過改變狀態後回去callbacks裏循環待執行的回調函數
          },
          onRejected(reason){
            handle(onRejected)
          }
        })
      }
    })
  }

  MyPromise.prototype.catch = function (onRejected) {               //定義then
    return this.then(undefined,onRejected)
  }
  window.MyPromise = MyPromise; // 導出MyPromise
})(window)
  • 相信你們看到這個代碼跟我是同樣的反應,我去,怎麼跟以前不同了
  • 實際上當我動手去實現了手寫Promise之後我以爲真的並不難,若是要說難這個then的實現,稍微比上面難一些。
  • 好了接下來我講解一下
  • 一、首先是在原來的基礎上去定義了一些常量方便使用

  • 二、要知道Promise.then返回的是什麼

  • 三、接着判斷一下狀態

  • 四、好的接下來咱們對相應的狀態進行操做

  • 五、能夠看到pendding狀態是沒有添加定時器的,爲何不是異步呢,由於是往callbacks存儲,而後上面執行到對應位置會進行循環判斷,且在循環操做時是異步的,,完善pendding

  • 六、前面的代碼能夠看得出有四個位置代碼類似度很高,因此定義一個函數,進行封裝(注意要在new Promise 裏)

  • 七、再設置一下默認值

  • 八、再來完善一下catch

接下來咱們進行測試

  • 看來沒爲何問題
  • 再來試試catch


  • 新手第一次寫的仍是不怎麼樣,可是每一步都是經過本身是如何學,實實在在的敲出來的,但願可以幫助到和我同樣想學好promise的朋友。從開始的很難到後面能直接寫出來,其實花費的時間仍是很多的,可是收貨頗豐啊。
相關文章
相關標籤/搜索