Promise原理—一步一步實現一個Promise


promise特點

一個promise的當前狀態只能是pending、fulfilled和rejected三種之一。狀態改變只能是pending到fulfilled或者pending到rejected。狀態改變不可逆。

支持鏈式調用。

(1) 原型方法

Promise.prototype.then = function() {}
Promise.prototype.catch = function() {}

(2) 靜態方法

Promise.resolve = function() {}
Promise.reject = function() {}
Promise.all = function() {}
Promise.race = function() {}
//...

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

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里的過程。

then的實現

在實現的then函數之前,我們來明確一下then函數要做那幾件事情。
1、注冊用戶回調到 _this.onResolvedCallback 或者 _this.onRejectedCallback
2、支持鏈式調用, 其實就是then函數執行完后應該返回一個promise對象,並且根據promise A+標准,這個promise應該是一個新的promise。
3、處理三種狀態, 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更進一步的處理,然后將數據交給下一步

/即我們要把onResolved/onRejected的返回值,x,
當成一個可能是Promise的對象,也即標准里所說的thenable,
並以最保險的方式調用x上的then方法,如果大家都按照標准實現,
那么不同的Promise之間就可以交互了。而標准為了保險起見,
即使x返回了一個帶有then屬性但並不遵循Promise標准的對象
/
遞歸解決,只要x帶有then方法,就會像剝洋蔥一樣層層的剝開,直到x是一個非類似promise的這種處理異步的對象,非thennable對象。

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
  }

其他靜態方法代碼參考

https://github.com/ab164287643/Promise3/blob/master/Promise3.js

本文參考

https://github.com/xieranmaya/blog/issues/3


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM