一步一步實現一個Promise

promise特色

一個promise的當前狀態只能是pending、fulfilled和rejected三種之一。狀態改變只能是pending到fulfilled或者pending到rejected。狀態改變不可逆。 支持鏈式調用。 (1) 原型方法git

Promise.prototype.then = function() {}
Promise.prototype.catch = function() {}
複製代碼

(2) 靜態方法github

Promise.resolve = function() {}
Promise.reject = function() {}
Promise.all = function() {}
Promise.race = function() {}
//...
複製代碼

Promise的優缺點
優勢:
狀態不可改變 鏈式調用解決回調地獄問題,讓代碼更清晰,更易維護。
缺點:
不能在執行中停止 在pending中不能查看異步到什麼狀態了。面試

promise源碼實現

首先咱們先看看咱們怎麼使用Promise的promise

new Promise(function (resolve, reject) {
    setTimeout(() => {
        // resolve("異步成功拉");
        reject("異步失敗啦");
    }, 1000);
}).then(
function (data) {
    console.log(data);

    /* then裏面能夠是同步的代碼,也能夠是異步的promise */
    // return new Promise(function (resolve, reject){
    //     setTimeout(() => {
    //         resolve("第一個then裏面的異步");
    //     }, 1000);
    // });

    return "鏈式調用第一個then的返回值";
},
function (reason) {
    console.log("第一個then" + reason);
    return "第一個then reject 後的 失敗 reason"
}
)
複製代碼

實現一個簡單的Promise構造函數

function Promise(executor) {
    var _this = this;
    this.data = undefined;//數據
    this.status = "pending";//狀態

    this.onResolvedCallback = [] // Promise resolve時的回調函數集,由於在Promise結束以前有可能有多個回調添加到它上面
    this.onRejectedCallback = [] // Promise reject時的回調函數集,由於在Promise結束以前有可能有多個回調添加到它上面

   var resolve = function (data){
        if (_this.status === "pending"){
            _this.status = "resolved";
            _this.data = data;
            for(var i = 0; i < _this.onResolvedCallback.length; i++) {
                _this.onResolvedCallback[i](data)
            }
        }
    }

    var reject = function (errReason) {
        if (_this.status === "pending"){
            _this.status = "rejected";
            _this.data = errReason;
            for(var i = 0; i < _this.onRejectedCallback.length; i++) {
                _this.onRejectedCallback[i](errReason)
            }
        }
    }
    try{
        executor(resolve, reject);
    } catch(e){
        reject(e);
    }
}
複製代碼

由上面的代碼能夠看出 executor通常來講應該是一個異步,等待其執行完後 成功或者失敗,而後執行其回調resolve或者reject。 而後在resolve或者reject裏面執行then裏面註冊的回調函數。因此then函數應該是一個註冊用戶回調 到 onResolvedCallback或者onRejectedCallback裏的過程。bash

then的實現

在實現的then函數以前,咱們來明確一下then函數要作那幾件事情。
一、註冊用戶回調到 _this.onResolvedCallback 或者 _this.onRejectedCallback
二、支持鏈式調用, 其實就是then函數執行完後應該返回一個promise對象,而且根據promise A+標準,這個promise應該是一個新的promise。
三、處理三種狀態, executor多是一個同步的函數也有多是一個異步的函數,因此在執行then的時候 _this.status 多是pending(executor是異步的狀況),_this.status 多是resolve/reject (executor是同步的狀況) 而status是pending的狀況下是一個註冊的過程,也就是將回調存起來,等待status變成resolve或者reject再執行回調。 而status是resolve/reject的狀況下就直接執行對調了。
上面這段解釋建議邊看下面的代碼邊理解上面這段話異步

//onResolved  onRejected 爲調用者傳進來的 成功和失敗的回掉
Promise.prototype.then = function (onResolved, onRejected){

    var _this = this
    var promise2;
    // 根據標準,若是then的參數不是function,則咱們須要忽略它,此處以以下方式處理
    onResolved = typeof onResolved === 'function' ? onResolved : function(value) {}
    onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {}

    //若是上面的executor是一個異步的,執行then的時候 status必定是pending
    if (this.status === "pending"){
        //生成一個新的promise
        promise2 = new Promise(function(resolve, reject) {
            //將調用者的回調包裝後註冊進promise的回調隊列
            _this.onResolvedCallback.push(function (value){
                //這裏的value是在onResolvedCallback裏面的函數執行時傳的
                try {
                    var x = onResolved(_this.data)
                    if (x instanceof Promise) {
                        //then裏面的回調若是是異步的promise,則等待異步執行完後,再進入promise2的then中註冊的回調
                        x.then(resolve, reject);
                    }
                    else{
                        //若是是同步的,直接進入promise2的then中註冊的回調
                        resolve(x);
                    }
                } catch (e) {
                    reject(e)
                }
            });

            _this.onRejectedCallback.push(function (reason) {
                try {
                    var x = onRejected(_this.data)
                    if (x instanceof Promise) {
                      x.then(resolve, reject);
                    }
                    else{
                        reject(x);
                    }
                } catch (e) {
                    reject(e)
                }
            });
        })
        return promise2;
    }

    //若是executor是同步的, 則執行then的時候 status爲 resolved或者rejected
    if (_this.status === 'resolved') {
        // 若是promise1(此處即爲this/_this)的狀態已經肯定而且是resolved,咱們調用onResolved
        // 由於考慮到有可能throw,因此咱們將其包在try/catch塊裏
        return promise2 = new Promise(function(resolve, reject) {
          try {
            var x = onResolved(_this.data)
            if (x instanceof Promise) { // 若是onResolved的返回值是一個Promise對象,直接取它的結果作爲promise2的結果
              x.then(resolve, reject)
            }
            resolve(x) // 不然,以它的返回值作爲promise2的結果
          } catch (e) {
            reject(e) // 若是出錯,以捕獲到的錯誤作爲promise2的結果
          }
        })
      }
    
      // 此處與前一個if塊的邏輯幾乎相同,區別在於所調用的是onRejected函數,就再也不作過多解釋
      if (_this.status === 'rejected') {
        return promise2 = new Promise(function(resolve, reject) {
          try {
            var x = onRejected(_this.data)
            if (x instanceof Promise) {
              x.then(resolve, reject)
            }
          } catch (e) {
            reject(e)
          }
        })
    }
}  
複製代碼

看完代碼後我繼續來解釋promise2中的處理過程,如今須要想的是如何在 promise2中如何執行完後如何將正確的數據交給下一個then。 因此須要判斷x(onResolved或者onRejected執行的結果)是一個什麼值,若是是一個普通的值則直接調用promise2的resolve或者reject,可是若是x是一個promise對象,則咱們須要等待這個promise對象狀態變成reosolve或者reject,也就是等待這個promise處理完異步任務(須要用到promise,裏面通常都是異步任務),因此調用x.then(resove,reject),這裏是直接將promise2的resolve/reject做爲回調的。也就是等待x這個promise對象執行完後,交給promise2的then裏面的回調,銜接整個鏈式的過程。函數

catch的實現

Promise.prototype.catch = function (onRejected) {
    return this.then(null, onRejected)
}
複製代碼

到此,promise的整個原理就算大部分完成了,其實理解起來並非那麼難,對不對。測試

確保x的處理,能與不一樣的promise進行交互

根據promise A+規範,上面在promise2中處理x並將處理值交給 promise2.then的回調的整個過程並無考慮到''不符合promise規範的對象並帶有then方法的狀況'',promise A+規範但願能以最保險的方式將x傳遞到promise2.then,即便x是一個不遵循promise規範,可是帶有then的對象也可以完美的處理。 因此須要對x更進一步的處理,而後將數據交給下一步ui

/即咱們要把onResolved/onRejected的返回值,x, 當成一個多是Promise的對象,也即標準裏所說的thenable, 並以最保險的方式調用x上的then方法,若是你們都按照標準實現, 那麼不一樣的Promise之間就能夠交互了。而標準爲了保險起見, 即便x返回了一個帶有then屬性但並不遵循Promise標準的對象/ 遞歸解決,只要x帶有then方法,就會像剝洋蔥同樣層層的剝開,直到x是一個非相似promise的這種處理異步的對象,非thennable對象。this

function resolvePromise(promise2, x, resolve, reject) {
    var then
    var thenCalledOrThrow = false
  
    if (promise2 === x) { // 對應標準2.3.1節
      return reject(new TypeError('Chaining cycle detected for promise!'))
    }
  
    if (x instanceof Promise) { // 對應標準2.3.2節
      // 若是x的狀態尚未肯定,那麼它是有可能被一個thenable決定最終狀態和值的
      // 因此這裏須要作一下處理,而不能一律的覺得它會被一個「正常」的值resolve
      if (x.status === 'pending') {
        x.then(function(value) {
          resolvePromise(promise2, value, resolve, reject)
        }, reject)
      } else { // 但若是這個Promise的狀態已經肯定了,那麼它確定有一個「正常」的值,而不是一個thenable,因此這裏直接取它的狀態
        x.then(resolve, reject)
      }
      return
    }
  
    if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) { // 2.3.3
      try {
  
        // 2.3.3.1 由於x.then有多是一個getter,這種狀況下屢次讀取就有可能產生反作用
        // 即要判斷它的類型,又要調用它,這就是兩次讀取
        then = x.then 
        if (typeof then === 'function') { // 2.3.3.3
          then.call(x, function rs(y) { // 2.3.3.3.1
            if (thenCalledOrThrow) return // 2.3.3.3.3 即這三處誰選執行就以誰的結果爲準
            thenCalledOrThrow = true
            return resolvePromise(promise2, y, resolve, reject) // 2.3.3.3.1
          }, function rj(r) { // 2.3.3.3.2
            if (thenCalledOrThrow) return // 2.3.3.3.3 即這三處誰選執行就以誰的結果爲準
            thenCalledOrThrow = true
            return reject(r)
          })
        } else { // 2.3.3.4
          resolve(x)
        }
      } catch (e) { // 2.3.3.2
        if (thenCalledOrThrow) return // 2.3.3.3.3 即這三處誰選執行就以誰的結果爲準
        thenCalledOrThrow = true
        return reject(e)
      }
    } else { // 2.3.4
      resolve(x)
    }
}
複製代碼

將promise2的處理過程改一下,三種狀況都要改。

promise2 = new Promise(function(resolve, reject) {
            //將調用者的回調包裝後註冊進promise的回調隊列
            _this.onResolvedCallback.push(function (value){
                //這裏的value是在onResolvedCallback裏面的函數執行時傳的
                try {
                    var x = onResolved(value);
                    //解決調用者定義的onResolved的返回值 x 是非規範的Promise對象且帶有then方法的狀況
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    reject(e)
                }
            });

            _this.onRejectedCallback.push(function (reason) {
                try {
                    var x = onRejected(reason)
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    reject(e)
                }
            });
        })
        return promise2;
複製代碼

測試一下

//測試不遵照promise的對象,且帶有then方法
var notPromise = function () {
    //
}

notPromise.prototype.then = function (onResolved, onRejected) {
    setTimeout(function () {
        onResolved("不遵照promise規範的對象")
    }, 1000)
}

//測試
new Promise(function (resolve, rejected) {
    setTimeout(function () {
        resolve("異步開始了");
    },1000)
}).then(function (data) {
    console.log(data);
    //下面的返回能夠是 promise  也能夠是普通的值, 還能夠是不許尋promise規範的對象但帶有then方法(在resolve都給與了支持)
    //普通值和promise就不測試了。
    //測試一下遵循promise的對象, 且帶有then方法
    return new notPromise();
}).then(function (data) {
    //在then裏面 會把上一個傳遞下來的值(new notPromise())不斷的調它的then方法,知道肯定沒有then能夠調用了,就遞交到下一個then
    console.log(data); // 這裏的 data 不是 (new notPromise())而是它的then方法返回的。
})
複製代碼

值穿透問題

new Promise(resolve=>resolve(8))
  .then()
  .catch()
  .then(function(value) {
    alert(value)
  })
複製代碼

跟下面這段代碼的行爲是同樣的

new Promise(resolve=>resolve(8))
  .then(function(value){
    return value
  })
  .catch(function(reason){
    throw reason
  })
  .then(function(value) {
    alert(value)
  })
複製代碼

不傳回調,則使用默認回調,因此在默認回調上改改,讓它將值傳遞下去

onResolved = typeof onResolved === 'function' ? onResolved : function(value) {return value}
onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {throw reason}
複製代碼

promise停止問題(面試題哦)

在一些場景下,咱們可能會遇到一個較長的Promise鏈式調用,在某一步中出現的錯誤讓咱們徹底沒有必要去運行鏈式調用後面全部的代碼,相似下面這樣(此處略去了then/catch裏的函數): 先給出結果

Promise.cancel = Promise.stop = function() {
    return new Promise(function(){})
}
//下面咱們來嘗試一下,若是遇到錯誤,則不會執行alert(1)
new Promise(function(resolve, reject) {
    resolve(42)
  })
    .then(function(value) {
        var isErr = true;  //嘗試更改爲 true 或者 false  看alert(1);是否執行
        if (isErr){
            // "Big ERROR!!!"
            return Promise.stop()
        }
      
    })
    //值的穿透
    .catch()
    .then()
    .then()
    .catch()
    .then(function () {
        alert(1);
    })
複製代碼

return new Promise(function(){})這段話就意味着x是一個promise對象, 則必定會走 x。then(resolve,reject) 交給promise2的then。可是這裏new Promise(function(){}根本就沒有resolve或者reject,因此它的狀態一直爲pending, 因此永遠不會執行 x.then(resolve,reject)裏面的resolve/reject,那麼狀態就傳遞不下去,鏈式就算是斷開了。

promise鏈上沒有catch等錯誤處理回調,怎麼看到錯誤

沒有錯誤處理函數,就給個默認的錯誤處理

function reject(reason) {
        setTimeout(function() {
          if (_this.status === 'pending') {
            _this.status = 'rejected'
            _this.data = reason
            if (_this.onRejectedCallback.length === 0) {
              console.error(reason)//默認的錯誤處理
            }
            for (var i = 0; i < _this.rejectedFn.length; i++) {
              _this.rejectedFn[i](reason)
            }
          }
        })
    }
複製代碼

Promise靜態方法的實現

列幾個比較經常使用,很好理解,看代碼基本就能明白,特別是Promise.all Promise.race的實現哦,面試常考原理。

Promise.all = function(promises) {
    return new Promise(function(resolve, reject) {
      var resolvedCounter = 0
      var promiseNum = promises.length
      var resolvedValues = new Array(promiseNum)
      for (var i = 0; i < promiseNum; i++) {
        (function(i) {
          Promise.resolve(promises[i]).then(function(value) {
            resolvedCounter++
            resolvedValues[i] = value
            if (resolvedCounter == promiseNum) {
              return resolve(resolvedValues)
            }
          }, function(reason) {
            return reject(reason)
          })
        })(i)
      }
    })
  }

  Promise.race = function(promises) {
    return new Promise(function(resolve, reject) {
      for (var i = 0; i < promises.length; i++) {
        Promise.resolve(promises[i]).then(function(value) {
          return resolve(value)
        }, function(reason) {
          return reject(reason)
        })
      }
    })
  }

  Promise.resolve = function(value) {
    var promise = new Promise(function(resolve, reject) {
      resolvePromise(promise, value, resolve, reject)
    })
    return promise
  }

  Promise.reject = function(reason) {
    return new Promise(function(resolve, reject) {
      reject(reason)
    })
  }
複製代碼

而後給一個完整版的代碼

try {
    module.exports = Promise
  } catch (e) {}
  
  function Promise(executor) {
    var self = this
  
    self.status = 'pending'
    self.onResolvedCallback = []
    self.onRejectedCallback = []
  
    function resolve(value) {
      if (value instanceof Promise) {
        return value.then(resolve, reject)
      }
      setTimeout(function() { // 異步執行全部的回調函數
        if (self.status === 'pending') {
          self.status = 'resolved'
          self.data = value
          for (var i = 0; i < self.onResolvedCallback.length; i++) {
            self.onResolvedCallback[i](value)
          }
        }
      })
    }
  
    function reject(reason) {
      setTimeout(function() { // 異步執行全部的回調函數
        if (self.status === 'pending') {
          self.status = 'rejected'
          self.data = reason
          for (var i = 0; i < self.onRejectedCallback.length; i++) {
            self.onRejectedCallback[i](reason)
          }
        }
      })
    }
  
    try {
      executor(resolve, reject)
    } catch (reason) {
      reject(reason)
    }
  }
  
  function resolvePromise(promise2, x, resolve, reject) {
    var then
    var thenCalledOrThrow = false
  
    if (promise2 === x) {
      return reject(new TypeError('Chaining cycle detected for promise!'))
    }
  
    if (x instanceof Promise) {
      if (x.status === 'pending') { //because x could resolved by a Promise Object
        x.then(function(v) {
          resolvePromise(promise2, v, resolve, reject)
        }, reject)
      } else { //but if it is resolved, it will never resolved by a Promise Object but a static value;
        x.then(resolve, reject)
      }
      return
    }
  
    if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) {
      try {
        then = x.then //because x.then could be a getter
        if (typeof then === 'function') {
          then.call(x, function rs(y) {
            if (thenCalledOrThrow) return
            thenCalledOrThrow = true
            return resolvePromise(promise2, y, resolve, reject)
          }, function rj(r) {
            if (thenCalledOrThrow) return
            thenCalledOrThrow = true
            return reject(r)
          })
        } else {
          resolve(x)
        }
      } catch (e) {
        if (thenCalledOrThrow) return
        thenCalledOrThrow = true
        return reject(e)
      }
    } else {
      resolve(x)
    }
  }
  
  Promise.prototype.then = function(onResolved, onRejected) {
    var self = this
    var promise2
    onResolved = typeof onResolved === 'function' ? onResolved : function(v) {
      return v
    }
    onRejected = typeof onRejected === 'function' ? onRejected : function(r) {
      throw r
    }
  
    if (self.status === 'resolved') {
      return promise2 = new Promise(function(resolve, reject) {
        setTimeout(function() { // 異步執行onResolved
          try {
            var x = onResolved(self.data)
            resolvePromise(promise2, x, resolve, reject)
          } catch (reason) {
            reject(reason)
          }
        })
      })
    }
  
    if (self.status === 'rejected') {
      return promise2 = new Promise(function(resolve, reject) {
        setTimeout(function() { // 異步執行onRejected
          try {
            var x = onRejected(self.data)
            resolvePromise(promise2, x, resolve, reject)
          } catch (reason) {
            reject(reason)
          }
        })
      })
    }
  
    if (self.status === 'pending') {
      // 這裏之因此沒有異步執行,是由於這些函數必然會被resolve或reject調用,而resolve或reject函數裏的內容已經是異步執行,構造函數裏的定義
      return promise2 = new Promise(function(resolve, reject) {
        self.onResolvedCallback.push(function(value) {
          try {
            var x = onResolved(value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (r) {
            reject(r)
          }
        })
  
        self.onRejectedCallback.push(function(reason) {
            try {
              var x = onRejected(reason)
              resolvePromise(promise2, x, resolve, reject)
            } catch (r) {
              reject(r)
            }
          })
      })
    }
  }
  
  Promise.prototype.catch = function(onRejected) {
    return this.then(null, onRejected)
  }
  
  Promise.deferred = Promise.defer = function() {
    var dfd = {}
    dfd.promise = new Promise(function(resolve, reject) {
      dfd.resolve = resolve
      dfd.reject = reject
    })
    return dfd
  }
複製代碼

其餘靜態方法代碼參考

github.com/ab164287643…

本文參考

github.com/xieranmaya/…

相關文章
相關標籤/搜索