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

陳磊同學發表於2019-04-27

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
  }
複製程式碼

其他靜態方法程式碼參考

github.com/ab164287643…

本文參考

github.com/xieranmaya/…

相關文章