用es5實現es6的promise,徹底搞懂promise的原理

楊陽本尊發表於2018-05-20

1.promise的含義

Promise 是非同步程式設計的一種解決方案,比傳統的解決方案——回撥函式和事件——更合理和更強大。它由社群最早提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise物件。

所謂Promise,簡單說就是一個容器,裡面儲存著某個未來才會結束的事件(通常是一個非同步操作)的結果。從語法上說,Promise 是一個物件,從它可以獲取非同步操作的訊息。Promise 提供統一的 API,各種非同步操作都可以用同樣的方法進行處理。

Promise物件有以下兩個特點。

(1)物件的狀態不受外界影響。Promise物件代表一個非同步操作,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)。只有非同步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。這也是Promise這個名字的由來,它的英語意思就是“承諾”,表示其他手段無法改變。

(2)一旦狀態改變,就不會再變,任何時候都可以得到這個結果。Promise物件的狀態改變,只有兩種可能:從pending變為fulfilled和從pending變為rejected。只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱為 resolved(已定型)。如果改變已經發生了,你再對Promise物件新增回撥函式,也會立即得到這個結果。這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監聽,是得不到結果的。

有了Promise物件,就可以將非同步操作以同步操作的流程表達出來,避免了層層巢狀的回撥函式。此外,Promise物件提供統一的介面,使得控制非同步操作更加容易。

Promise也有一些缺點。首先,無法取消Promise,一旦新建它就會立即執行,無法中途取消。其次,如果不設定回撥函式,Promise內部丟擲的錯誤,不會反應到外部。第三,當處於pending狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。

2.小試promise用法:

Promise建構函式接受一個函式作為引數,該函式的兩個引數分別是resolvereject。它們是兩個函式,由 JavaScript 引擎提供,不用自己部署。

resolve函式的作用是,將Promise物件的狀態從“未完成”變為“成功”(即從 pending 變為 resolved),在非同步操作成功時呼叫,並將非同步操作的結果,作為引數傳遞出去;reject函式的作用是,將Promise物件的狀態從“未完成”變為“失敗”(即從 pending 變為 rejected),在非同步操作失敗時呼叫,並將非同步操作報出的錯誤,作為引數傳遞出去。


var p=new Promise(function(resolve,rejcet){
    setTimeout(function(){
        if(true){
            //非同步操作成功
            resolve('success');
        }else{
            //非同步操作失敗
            rejcet('failure');
        }
    },1000);
});
p.then(function(value){
    //成功的回撥
    console.log(value);
},function(error){
    //失敗的回撥
    console.log(error);
});複製程式碼

從demo中可以看到,在ES6中Promise物件是一個建構函式,用來生成Promise例項。並且Promise例項生成以後,可以用then方法分別指定resolved狀態和rejected狀態的回撥函式。

3.手寫一個promise,徹底掌握它的原理

首先,我們先搭建好程式碼的骨架:

function Promise(callback) {
  var self = this //promise例項
  self.status = 'PENDING' // Promise當前的狀態
  self.data = undefined  // Promise的值
  self.onResolvedCallback = [] // Promise resolve時的回撥函式集
  self.onRejectedCallback = [] // Promise reject時的回撥函式集
  callback(resolve, reject) // 執行executor並傳入相應的引數

  function resolve(value){

  }
  function reject(error){

  }
}
// 新增我們的then方法
Promise.prototype.then=function(){

}複製程式碼

我們先建立一個Promise建構函式,並傳入一個回撥函式callback,callback裡面傳入兩個函式作為引數,一個是resove,一個是reject。並在Promise的原型上加入我們的then方法。框架搭好了,接下來我們來一點點的完善框架裡面的內容,可以這麼說,把resolve,reject和then補充完,基本可以說就是把Promise完成了。

然後緊接著,我們完善一下resolve和reject:

function Promise(callback) {
  var self = this
  self.status = 'PENDING' // Promise當前的狀態
  self.data = undefined  // Promise的值
  self.onResolvedCallback = [] // Promise resolve時的回撥函式集
  self.onRejectedCallback = [] // Promise reject時的回撥函式集
  callback(resolve, reject) // 執行executor並傳入相應的引數

  function resolve(value){
      if(self.status=='PENDING'){
          self.status=='FULFILLED';
          self.data=value;
          // 依次執行成功之後的函式棧
          for(var i = 0; i < self.onResolvedCallback.length; i++) {
            self.onResolvedCallback[i](value)
          }
      }
  }

  function rejecte(error){
    if (self.status === 'PENDING') {
       self.status = 'REJECTED'
       self.data = error;
       // 依次執行失敗之後的函式棧
       for(var i = 0; i < self.onRejectedCallback.length; i++) {
           self.onRejectedCallback[i](error)
        }
    }
  }
}複製程式碼

接下來我們實現我們的then方法:

then方法是Promise的核心,因此這裡會花比較大的篇幅去介紹then:

一個promise的then接受兩個引數:

promise.then(onFulfilled, onRejected)複製程式碼

onFulfilledonRejected 都是可選引數。

  • 如果 onFulfilled 不是函式,其必須被忽略
  • 如果 onRejected 不是函式,其必須被忽略

onFulfilled 特性

如果 onFulfilled 是函式:

  • promise 執行結束後其必須被呼叫,其第一個引數為 promise 的終值,也就是resolve傳過來的值
  • promise 執行結束前其不可被呼叫
  • 其呼叫次數不可超過一次

onRejected 特性

如果 onRejected 是函式:

  • promise 被拒絕執行後其必須被呼叫,其第一個引數為 promise 的據因,也就是reject傳過來的值
  • promise 被拒絕執行前其不可被呼叫
  • 其呼叫次數不可超過一次

呼叫時機

onFulfilledonRejected 只有在執行環境堆疊僅包含平臺程式碼時才可被呼叫(平臺程式碼指引擎、環境以及 promise 的實施程式碼)

呼叫要求

onFulfilledonRejected 必須被作為函式呼叫(即沒有 this 值,在嚴格模式(strict) 中,函式 this 的值為 undefined ;在非嚴格模式中其為全域性物件。)

多次呼叫

then 方法可以被同一個 promise 呼叫多次

  • promise 成功執行時,所有 onFulfilled 需按照其註冊順序依次回撥
  • promise 被拒絕執行時,所有的 onRejected 需按照其註冊順序依次回撥

返回

then 方法必須返回一個 promise 物件

promise2 = promise1.then(onFulfilled, onRejected);複製程式碼
  • 如果 onFulfilled 或者 onRejected 返回一個值 x ,則執行下面的 Promise 解決過程[[Resolve]](promise2, x)
  • 如果 onFulfilled 或者 onRejected 丟擲一個異常 e ,則 promise2 必須拒絕執行,並返回拒因 e
  • 如果 onFulfilled 不是函式且 promise1 成功執行, promise2 必須成功執行並返回相同的值
  • 如果 onRejected 不是函式且 promise1 拒絕執行, promise2 必須拒絕執行並返回相同的據因

不論 promise1 被 reject 還是被 resolve 時 promise2 都會被 resolve,只有出現異常時才會被 rejected

每個Promise物件都可以在其上多次呼叫then方法,而每次呼叫then返回的Promise的狀態取決於那一次呼叫then時傳入引數的返回值,所以then不能返回this,因為then每次返回的Promise的結果都有可能不同。

接下來我們來寫我們的then方法:

Promise.prototype.then = function(onResolved, onRejected) {
  var self = this
  var promise2

  // 根據標準,如果then的引數不是function,則我們需要忽略它,此處以如下方式處理
  onResolved = typeof onResolved === 'function' ? onResolved : function(value) {}
  onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {}

  if (self.status === 'resolved') {
    // 如果promise1(此處即為this/self)的狀態已經確定並且是resolved,我們呼叫onResolved
    // 因為考慮到有可能throw,所以我們將其包在try/catch塊裡
    return promise2 = new Promise(function(resolve, reject) {
      try {
        var x = onResolved(self.data)
        if (x instanceof Promise) { // 如果onResolved的返回值是一個Promise物件,直接取它的結果做為promise2的結果
          x.then(resolve, reject)
        }
        resolve(x) // 否則,以它的返回值做為promise2的結果
      } catch (e) {
        reject(e) // 如果出錯,以捕獲到的錯誤做為promise2的結果
      }
    })
  }

  // 此處與前一個if塊的邏輯幾乎相同,區別在於所呼叫的是onRejected函式,就不再做過多解釋
  if (self.status === 'rejected') {
    return promise2 = new Promise(function(resolve, reject) {
      try {
        var x = onRejected(self.data)
        if (x instanceof Promise) {
          x.then(resolve, reject)
        }
      } catch (e) {
        reject(e)
      }
    })
  }

  if (self.status === 'pending') {
  // 如果當前的Promise還處於pending狀態,我們並不能確定呼叫onResolved還是onRejected,
  // 只能等到Promise的狀態確定後,才能確實如何處理。
  // 所以我們需要把我們的**兩種情況**的處理邏輯做為callback放入promise1(此處即this/self)的回撥陣列裡
  // 邏輯本身跟第一個if塊內的幾乎一致,此處不做過多解釋
    return promise2 = new Promise(function(resolve, reject) {
      self.onResolvedCallback.push(function(value) {
        try {
          var x = onResolved(self.data)
          if (x instanceof Promise) {
            x.then(resolve, reject)
          }
        } catch (e) {
          reject(e)
        }
      })

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

// 為了下文方便,我們順便實現一個catch方法
Promise.prototype.catch = function(onRejected) {
  return this.then(null, onRejected)
}複製程式碼

最後附上完整的程式碼:

Promise.prototype.then = function(onResolved, onRejected) {
  var self = this
  var promise2

  // 根據標準,如果then的引數不是function,則我們需要忽略它,此處以如下方式處理
  onResolved = typeof onResolved === 'function' ? onResolved : function(value) {}
  onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {}

  if (self.status === 'resolved') {
    // 如果promise1(此處即為this/self)的狀態已經確定並且是resolved,我們呼叫onResolved
    // 因為考慮到有可能throw,所以我們將其包在try/catch塊裡
    return promise2 = new Promise(function(resolve, reject) {
      try {
        var x = onResolved(self.data)
        if (x instanceof Promise) { // 如果onResolved的返回值是一個Promise物件,直接取它的結果做為promise2的結果
          x.then(resolve, reject)
        }
        resolve(x) // 否則,以它的返回值做為promise2的結果
      } catch (e) {
        reject(e) // 如果出錯,以捕獲到的錯誤做為promise2的結果
      }
    })
  }

  // 此處與前一個if塊的邏輯幾乎相同,區別在於所呼叫的是onRejected函式,就不再做過多解釋
  if (self.status === 'rejected') {
    return promise2 = new Promise(function(resolve, reject) {
      try {
        var x = onRejected(self.data)
        if (x instanceof Promise) {
          x.then(resolve, reject)
        }
      } catch (e) {
        reject(e)
      }
    })
  }

  if (self.status === 'pending') {
  // 如果當前的Promise還處於pending狀態,我們並不能確定呼叫onResolved還是onRejected,
  // 只能等到Promise的狀態確定後,才能確實如何處理。
  // 所以我們需要把我們的**兩種情況**的處理邏輯做為callback放入promise1(此處即this/self)的回撥陣列裡
  // 邏輯本身跟第一個if塊內的幾乎一致,此處不做過多解釋
    return promise2 = new Promise(function(resolve, reject) {
      self.onResolvedCallback.push(function(value) {
        try {
          var x = onResolved(self.data)
          if (x instanceof Promise) {
            x.then(resolve, reject)
          }
        } catch (e) {
          reject(e)
        }
      })

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

// 為了下文方便,我們順便實現一個catch方法
Promise.prototype.catch = function(onRejected) {
  return this.then(null, onRejected)
}複製程式碼


相關文章