從零一步一步實現一個完整版的Promise

Promise A+ 規範
中文翻譯版地址
英文原版地址node

Promise標準解讀

1. promise至關於一個狀態機

擁有三種狀態:git

  • pendinggithub

  • fulfillednpm

  • rejected數組

(1) promise 對象初始化狀態爲 pendingpromise

(2) 當調用resolve(成功),會由pending => fulfilled框架

(3) 當調用reject(失敗),會由pending => rejected異步

2. promise對象方法

  1. 標準中只有一個then方法,沒有catch,race,all等方法,甚至沒有構造函數 Promise標準中僅指定了Promise對象的then方法的行爲,其它一切咱們常見的方法/函數都並無指定,包括catch,race,all等經常使用方法,甚至也沒有指定該如何構造出一個Promise對象,另外then也沒有通常實現中(Q, $q等)所支持的第三個參數,通常稱onProgressasync

  2. then方法返回一個新的Promise Promise的then方法返回一個新的Promise,而不是返回this,此處在下文會有更多解釋函數

    promise2 = promise1.then(alert)
    promise2 != promise1 // true
    複製代碼
  3. 不一樣Promise的實現須要能夠相互調用(interoperable)

  4. Promise的初始狀態爲pending,它能夠由此狀態轉換爲fulfilled或者rejected,一旦狀態肯定,就不能夠再次轉換爲其它狀態,狀態肯定的過程稱爲settle

3. Promise的其餘方法

除了上文中說的then方法,本文實現的Promise會在基於標準之上,新增catch,race,all,resolve,reject方法

  1. Promise.prototype.catch,在鏈式寫法中能夠捕獲前面的異常

    promise.catch(onRejected)
    // 至關於
    promise.then(null, onRrejected);
    
    // 注意
    // onRejected 不能捕獲當前onFulfilled中的異常
    promise.then(onFulfilled, onRrejected); 
    
    // 能夠寫成:
    promise.then(onFulfilled)
          .catch(onRrejected); 
    複製代碼
  2. Promise.resolve,返回一個fulfilled狀態的promise對象

    Promise.resolve('hello').then(function(value){
      console.log(value);
    });
    
    Promise.resolve('hello');
    // 至關於
    const promise = new Promise(resolve => {
      resolve('hello');
    });
    複製代碼
  3. Promise.reject,返回一個rejected狀態的promise對象

    Promise.reject(24);
    new Promise((resolve, reject) => {
      reject(24);
    });
    複製代碼
  4. Promise.all,接收一個promise對象數組爲參數 只有所有 promise 進入 fulfilled 狀態纔會resolve 一般會用來處理 多個並行異步操做

    const p1 = new Promise((resolve, reject) => {
        resolve(1);
    });
    
    const p2 = new Promise((resolve, reject) => {
        resolve(2);
    });
    
    const p3 = new Promise((resolve, reject) => {
        resolve(3);
    });
    
    Promise.all([p1, p2, p3]).then(data => { 
        console.log(data); // [1, 2, 3] 結果順序和promise實例數組順序是一致的
    }, err => {
        console.log(err);
    });
    複製代碼
  5. Promise.race,接收一個promise對象數組爲參數 Promise.race 只要有一個promise對象進入 fulfilled 或者 rejected 狀態的話,就會繼續進行後面的處理

    function timerPromisefy(delay) {
      return new Promise(function (resolve, reject) {
          setTimeout(function () {
              resolve(delay);
          }, delay);
      });
    }
    var startDate = Date.now();
    
    Promise.race([
        timerPromisefy(10),
        timerPromisefy(20),
        timerPromisefy(30)
    ]).then(function (values) {
        console.log(values); // 10
    });
    複製代碼

一步一步實現一個Promise

下面咱們就來一步一步實現一個Promise

構造函數

由於標準並無指定如何構造一個Promise對象,因此咱們一樣以目前通常Promise實現中通用的方法來構造一個Promise對象,也是ES6原生Promise裏所使用的方式,即:

// Promise構造函數接收一個executor函數,executor函數執行完同步或異步操做後,調用它的兩個參數resolve和reject
var promise = new Promise(function(resolve, reject) {
  /* 若是操做成功,調用resolve並傳入value 若是操做失敗,調用reject並傳入reason */
})
複製代碼

咱們先實現構造函數的框架以下:

function Promise(executor) {
  var self = this
  self.status = 'pending' // Promise當前的狀態
  self.data = undefined  // Promise的值
  self.onResolvedCallback = [] // Promise resolve時的回調函數集,由於在Promise結束以前有可能有多個回調添加到它上面
  self.onRejectedCallback = [] // Promise reject時的回調函數集,由於在Promise結束以前有可能有多個回調添加到它上面

  executor(resolve, reject) // 執行executor並傳入相應的參數
}
複製代碼

上面的代碼基本實現了Promise構造函數的主體,但目前還有兩個問題:

  1. 咱們給executor函數傳了兩個參數:resolve和reject,這兩個參數目前尚未定義

  2. executor有可能會出錯(throw),相似下面這樣,而若是executor出錯,Promise應該被其throw出的值reject:

    new Promise(function(resolve, reject) {
      throw 2
    })
    複製代碼

因此咱們須要在構造函數裏定義resolve和reject這兩個函數:

function Promise(executor) {
  var self = this
  self.status = 'pending' // Promise當前的狀態
  self.data = undefined  // Promise的值
  self.onResolvedCallback = [] // Promise resolve時的回調函數集,由於在Promise結束以前有可能有多個回調添加到它上面
  self.onRejectedCallback = [] // Promise reject時的回調函數集,由於在Promise結束以前有可能有多個回調添加到它上面

  function resolve(value) {
    // TODO
  }

  function reject(reason) {
    // TODO
  }

  try { // 考慮到執行executor的過程當中有可能出錯,因此咱們用try/catch塊給包起來,而且在出錯後以catch到的值reject掉這個Promise
    executor(resolve, reject) // 執行executor
  } catch(e) {
    reject(e)
  }
}
複製代碼

接下來,咱們實現resolve和reject這兩個函數

function Promise(executor) {
  // ...

  function resolve(value) {
    if (self.status === 'pending') {
      self.status = 'fulfilled'
      self.data = value
      //執行resolve的回調函數,將value傳遞到callback中
      self.onResolvedCallback.forEach(callback => callback(value))
    }
  }

  function reject(reason) {
    if (self.status === 'pending') {
      self.status = 'rejected'
      self.data = reason
      //執行reject的回調函數,將reason傳遞到callback中
      self.onRejectedCallback.forEach(callback => callback(reason))
    }
  }

  // ...
}

複製代碼

基本上就是在判斷狀態爲pending以後把狀態改成相應的值,並把對應的value和reason存在self的data屬性上面,以後執行相應的回調函數,邏輯很簡單,這裏就很少解釋了。

then方法

Promise對象有一個then方法,用來註冊在這個Promise狀態肯定後的回調,很明顯,then方法須要寫在原型鏈上。then方法會返回一個Promise,關於這一點,Promise/A+標準並無要求返回的這個Promise是一個新的對象,但在Promise/A標準中,明確規定了then要返回一個新的對象,目前的Promise實現中then幾乎都是返回一個新的Promise對象,因此在咱們的實現中,也讓then返回一個新的Promise對象。

關於這一點,我認爲標準中是有一點矛盾的:

標準中說,若是promise2 = promise1.then(onResolved, onRejected)裏的onResolved/onRejected返回一個Promise,則promise2直接取這個Promise的狀態和值爲己用,但考慮以下代碼:

promise2 = promise1.then(function foo(value) {
  return Promise.reject(3)
})
複製代碼

此處若是foo運行了,則promise1的狀態必然已經肯定且爲resolved,若是then返回了this(即promise2 === promise1),說明promise2和promise1是同一個對象,而此時promise1/2的狀態已經肯定,沒有辦法再取Promise.reject(3)的狀態和結果爲己用,由於Promise的狀態肯定後就不可再轉換爲其它狀態。

另外每一個Promise對象均可以在其上屢次調用then方法,而每次調用then返回的Promise的狀態取決於那一次調用then時傳入參數的返回值,因此then不能返回this,由於then每次返回的Promise的結果都有可能不一樣。

下面咱們來實現then方法:

// then方法接收兩個參數,onResolved,onRejected,分別爲Promise成功或失敗後的回調
Promise.prototype.then = function (onResolve, onReject) {
  let self = this
  let promise2

  // 根據標準,若是then的參數不是function,則咱們須要忽略它,此處以以下方式處理
  onResolve = typeof onResolve==='function' ? onResolve : function(value){}
  onReject = typeof onReject==='function' ? onReject : function(reason){}
  
  if (self.status === 'pending') {
     return promise2 = new Promise(function(resolve, reject){

    })
  }

  if (self.status === 'fulfilled') {
    return promise2 = new Promise(function(resolve, reject){

    })
  }

  if (self.status === 'rejected') {
    return promise2 = new Promise(function(resolve, reject){

    })
  }
}
複製代碼

Promise總共有三種可能的狀態,咱們分三個if塊來處理,在裏面分別都返回一個new Promise。

根據標準,咱們知道,對於以下代碼,promise2的值取決於then裏面函數的返回值:

promise2 = promise1.then(function(value) {
  return 4
}, function(reason) {
  throw new Error('sth went wrong')
})
複製代碼

若是promise1被resolve了,promise2的將被4 resolve,若是promise1被reject了,promise2將被new Error('sth went wrong') reject,更多複雜的狀況再也不詳述。

因此,咱們須要在then裏面執行onResolved或者onRejected,並根據返回值(標準中記爲x)來肯定promise2的結果,而且,若是onResolved/onRejected返回的是一個Promise,promise2將直接取這個Promise的結果:

// then方法接收兩個參數,onResolved,onRejected,分別爲Promise成功或失敗後的回調
Promise.prototype.then = function (onResolve, onReject) {
  let self = this
  let promise2

  // 根據標準,若是then的參數不是function,則咱們須要忽略它,此處以以下方式處理
  onResolve = typeof onResolve==='function' ? onResolve : function(value){}
  onReject = typeof onReject==='function' ? onReject : function(reason){}
  
  if (self.status === 'fulfilled') {
    // 若是promise1(此處即爲this/self)的狀態已經肯定而且是fulfilled,咱們調用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)
        } else {
          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)
        } else {
          resolve(x)
        }
      } 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)
          } else {
            resolve(x)
          }
        } catch (e) {
          reject(e)
        }
      })

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

}

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

至此,咱們基本實現了Promise標準中所涉及到的內容,但還有幾個問題:

  1. 不一樣的Promise實現之間須要無縫的可交互,即Q的Promise,ES6的Promise,和咱們實現的Promise之間以及其它的Promise實現,應該而且是有必要無縫相互調用的,好比:
    // 此處用MyPromise來表明咱們實現的Promise
    new MyPromise(function(resolve, reject) { // 咱們實現的Promise
      setTimeout(function() {
        resolve(42)
      }, 2000)
    }).then(function() {
      return new Promise.reject(2) // ES6的Promise
    }).then(function() {
      return Q.all([ // Q的Promise
        new MyPromise(resolve=>resolve(8)), // 咱們實現的Promise
        new Promise.resolve(9), // ES6的Promise
        Q.resolve(9) // Q的Promise
      ])
    })
    複製代碼
    咱們前面實現的代碼並無處理這樣的邏輯,咱們只判斷了onResolved/onRejected的返回值是否爲咱們實現的Promise的實例,並無作任何其它的判斷,因此上面這樣的代碼目前是沒有辦法在咱們的Promise里正確運行的。
  2. 下面這樣的代碼目前也是沒辦法處理的:
    new Promise(resolve=>resolve(8))
      .then()
      .then()
      .then(function foo(value) {
        alert(value)
      })
    複製代碼
    正確的行爲應該是alert出8,而若是拿咱們的Promise,運行上述代碼,將會alert出undefined。這種行爲稱爲穿透,即8這個值會穿透兩個then(說Promise更爲準確)到達最後一個then裏的foo函數裏,成爲它的實參,最終將會alert出8。

下面咱們首先處理簡單的狀況,值的穿透

Promise值的穿透

經過觀察,會發現咱們但願下面這段代碼

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)
  })
複製代碼

因此若是想要把then的實參留空且讓值能夠穿透到後面,意味着then的兩個參數的默認值分別爲function(value) {return value},function(reason) {throw reason}

因此咱們只須要把then裏判斷onResolved和onRejected的部分改爲以下便可:

onResolved = typeof onResolved === 'function' ? onResolved : function(value) {return value}
onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {throw reason}
複製代碼
因而Promise神奇的值的穿透也沒有那麼黑魔法,只不過是then默認參數就是把值日後傳或者拋
不一樣Promise的交互

關於不一樣Promise間的交互,其實標準裏是有說明的,其中詳細指定了如何經過then的實參返回的值來決定promise2的狀態,咱們只須要按照標準把標準的內容轉成代碼便可。

這裏簡單解釋一下標準:

即咱們要把onResolved/onRejected的返回值,x,當成一個多是Promise的對象,也即標準裏所說的thenable,並以最保險的方式調用x上的then方法,若是你們都按照標準實現,那麼不一樣的Promise之間就能夠交互了。而標準爲了保險起見,即便x返回了一個帶有then屬性但並不遵循Promise標準的對象(好比說這個x把它then裏的兩個參數都調用了,同步或者異步調用(PS,原則上then的兩個參數須要異步調用,下文會講到),或者是出錯後又調用了它們,或者then根本不是一個函數),也能儘量正確處理。

關於爲什麼須要不一樣的Promise實現可以相互交互,我想緣由應該是顯然的,Promise並非JS一早就有的標準,不一樣第三方的實現之間是並不相互知曉的,若是你使用的某一個庫中封裝了一個Promise實現,想象一下若是它不能跟你本身使用的Promise實現交互的場景。。。

建議各位對照着標準閱讀如下代碼,由於標準對此說明的很是詳細,因此你應該可以在任意一個Promise實現中找到相似的代碼:

/* resolvePromise函數即爲根據x的值來決定promise2的狀態的函數 也即標準中的[Promise Resolution Procedure](https://promisesaplus.com/#point-47) x爲`promise2 = promise1.then(onResolved, onRejected)`裏`onResolved/onRejected`的返回值 `resolve`和`reject`其實是`promise2`的`executor`的兩個實參,由於很難掛在其它的地方,因此一併傳進來。 相信各位必定能夠對照標準把標準轉換成代碼,這裏就只標出代碼在標準中對應的位置,只在必要的地方作一些解釋 */
function resolvePromise (promise2, x, resolve, reject) {

  let then 
  let thenCalledOrThrow = false

  if (promise2 === x) { // 對應標準2.3.1節
    // 這裏可能有童鞋會問,何時會觸發promise2 === x這個條件,首先咱們對標準2.3.1節中的promise2===x在作下解釋
    // 條件promise === x ,至關於promise.then以後return了本身,由於then會等待return後的promise,致使本身等待本身,一直處於等待。
    /** 好比: let p1 = new Promise(function(resolve, reject) { setTimeout(() => {reject(1.1)}, 1000) }) let p1then = p1.then(function (value) { return p1then }, function (err) { return p1then }) p1then.then(value => { console.log('value:', value) }, err => { console.log('err:', err) }) 這段代碼就會觸發promise2 === x的判斷,咱們爲了promise不一直處於等待狀態,根據標準規範, 這裏咱們須要拋出'Chaining cycle detected for promise',即‘循環引用’的錯誤信息 */
    reject(new TypeError('Chaining cycle detected for promise!'))
    return
  }

  if (x instanceof Promise) { // 對應標準2.3.2節
    // 若是x的狀態尚未肯定,那麼它是有可能被一個thenable決定最終狀態和值的
    // 因此這裏須要作一下處理,而不能一律的覺得它會被一個「正常」的值resolve
    if (x.status === 'pending') {
      x.then(value => {
        resolvePromise(promise2, value, resolve, reject)
      }, err => {
        reject(err)
      })
    }  else { // 但若是這個Promise的狀態已經肯定了,那麼它確定有一個「正常」的值,而不是一個thenable,因此這裏直接取它的狀態
      x.then(resolve, reject)
    }
    return
  }

  if ((x !== null) && ((typeof x === 'function') || (typeof x === 'object'))) {
    try {
      // 2.3.3.1 由於x.then有多是一個getter,這種狀況下屢次讀取就有可能產生反作用
      // 即要判斷它的類型,又要調用它,這就是兩次讀取
      then = x.then //because x.then could be a getter
      if (typeof then === 'function') { // 2.3.3.3
        then.call(x, value => { // 2.3.3.3.1
          if (thenCalledOrThrow) return // 2.3.3.3.3 即這三處誰選執行就以誰的結果爲準
          thenCalledOrThrow = true
          resolvePromise(promise2, value, resolve, reject) // 2.3.3.3.1
          return
        }, err => { // 2.3.3.3.2
          if (thenCalledOrThrow) return // 2.3.3.3.3 即這三處誰選執行就以誰的結果爲準
          thenCalledOrThrow = true
          reject(err)
          return
        })
      } else { // 2.3.3.4
        resolve(x)
      }
    } catch (e) { // 2.3.3.2
      if (thenCalledOrThrow) return // 2.3.3.3.3 即這三處誰選執行就以誰的結果爲準
      thenCalledOrThrow = true
      reject(e)
      return
    }
  } else { // 2.3.4
    resolve(x)
  }

}
複製代碼

而後咱們使用這個函數的調用替換then裏幾處判斷x是否爲Promise對象的位置便可,見下方完整代碼。

最後,咱們剛剛說到,原則上,promise.then(onResolved, onRejected)裏的這兩個函數須要異步調用,關於這一點,標準裏也有說明:

In practice, this requirement ensures that onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack.

因此咱們須要對咱們的代碼作一點變更,即在四個地方加上setTimeout(fn, 0)

Tip: 咱們這裏增長setTimeout,涉及到js執行棧以及js單線程和eventLoop相關的知識,各位對js的執行棧、js單線程、eventLoop不太瞭解的,能夠谷歌查閱下相關資料,後邊我有空也會寫一篇js執行棧、js的單線程、eventloop的講解文章。下面的代碼中,我也會簡單寫一些加入setTimeout的緣由分析。

function Promise (executor) {
  let self = this
  self.status = 'pending' //Promise當前狀態
  self.data = undefined //當前Promise的值
  self.onResolvedCallback = [] //Promise resolve時的回調函數集合
  self.onRejectedCallback = [] //Promise reject時的回調函數集合

  function resolve (value) { // value成功態時接收的終值
    if (value instanceof Promise) {
      value.then(resolve, reject)
      return
    }

    // 爲何resolve 加setTimeout?
    // 2.2.4規範 onFulfilled 和 onRejected 只容許在 execution context 棧僅包含平臺代碼時運行.
    // 注1 這裏的平臺代碼指的是引擎、環境以及 promise 的實施代碼。實踐中要確保 onFulfilled 和 onRejected 方法異步執行,且應該在 then 方法被調用的那一輪事件循環以後的新執行棧中執行。
    setTimeout(function(){
      // 調用resolve 回調對應onFulfilled函數
      if (self.status === 'pending') {
        // 只能由pending狀態 => fulfilled狀態 (避免調用屢次resolve reject)
        self.status = 'fulfilled'
        self.data = value
        //執行resolve的回調函數,將value傳遞到callback中
        self.onResolvedCallback.forEach(callback => callback(value))
      }
    })
  }

  function reject (reason) { // reason失敗態時接收的拒因
    setTimeout(function(){
      // 調用reject 回調對應onRejected函數
      if (self.status === 'pending') {
        // 只能由pending狀態 => rejected狀態 (避免調用屢次resolve reject)
        self.status = 'rejected'
        self.data = reason
        //執行reject的回調函數,將reason傳遞到callback中
        self.onRejectedCallback.forEach(callback => callback(reason))
      }
    })
  }

  try {
    executor(resolve, reject)
  } catch (e) {
    reject(e)
  }
  
}

/** * [註冊fulfilled狀態/rejected狀態對應的回調函數] * @param {function} onFulfilled fulfilled狀態時 執行的函數 * @param {function} onRejected rejected狀態時 執行的函數 * @return {function} newPromsie 返回一個新的promise對象 */
Promise.prototype.then = function (onResolve, onReject) {
  let self = this
  let promise2

  // 處理參數默認值 保證參數後續可以繼續執行
  onResolve = typeof onResolve==='function' ? onResolve : function(value){return value}
  onReject = typeof onReject==='function' ? onReject : function(reason){throw reason}
  
  if (self.status === 'pending') { // 等待態
     return promise2 = new Promise(function(resolve, reject){
      
      // 當異步調用resolve/rejected時 將onFulfilled/onRejected收集暫存到集合中
      self.onResolvedCallback.push(function(value){
        try {
          let x = onResolve(value)
          resolvePromise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      })

      self.onRejectedCallback.push(function(reason){
        try {
          let x = onReject(reason)
          resolvePromise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      })
    })
  }

  // then裏面的FULFILLED/REJECTED狀態時 爲何要加setTimeout ?
  // 緣由:
  // 其一 2.2.4規範 要確保 onFulfilled 和 onRejected 方法異步執行(且應該在 then 方法被調用的那一輪事件循環以後的新執行棧中執行) 因此要在resolve里加上setTimeout
  // 其二 2.2.6規範 對於一個promise,它的then方法能夠調用屢次.(當在其餘程序中屢次調用同一個promise的then時 因爲以前狀態已經爲FULFILLED/REJECTED狀態,則會走的下面邏輯),因此要確保爲FULFILLED/REJECTED狀態後 也要異步執行onFulfilled/onRejected

  // 其二 2.2.6規範 也是resolve函數里加setTimeout的緣由
  // 總之都是 讓then方法異步執行 也就是確保onFulfilled/onRejected異步執行

  // 以下面這種情景 屢次調用p1.then
  // p1.then((value) => { // 此時p1.status 由pending狀態 => fulfilled狀態
  // console.log(value); // resolve
  // // console.log(p1.status); // fulfilled
  // p1.then(value => { // 再次p1.then 這時已經爲fulfilled狀態 走的是fulfilled狀態判斷裏的邏輯 因此咱們也要確保判斷裏面onFuilled異步執行
  // console.log(value); // 'resolve'
  // });
  // console.log('當前執行棧中同步代碼');
  // })
  // console.log('全局執行棧中同步代碼');
  //
  if (self.status === 'fulfilled') { // 成功態
    return promise2 = new Promise(function(resolve, reject){
      setTimeout(function(){
        try {
          let x = onResolve(self.data)
          resolvePromise(promise2, x, resolve, reject) // 新的promise resolve 上一個onFulfilled的返回值
        } catch (e) {
          reject(e) // 捕獲前面onFulfilled中拋出的異常 then(onFulfilled, onRejected);
        }
      },0)
    })
  }

  if (self.status === 'rejected') { // 失敗態
    return promise2 = new Promise(function(resolve, reject){
      setTimeout(function(){
        try {
          let x = onReject(self.data)
          resolvePromise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      },0)
    })
  }
}
複製代碼

其餘Promise的方法

Promise.prototype.catch,

功能是 在鏈式寫法中能夠捕獲前面的異常

實現原理相似於then的第二個參數

好比這樣:

// 用於promise方法鏈時 捕獲前面onFulfilled/onRejected拋出的異常
Promise.prototype.catch = function (onReject) {
  return this.then(null, onReject)
}
複製代碼
Promise.resolve

返回一個fulfilled狀態的promise對象

// 返回一個fulfilled狀態的promise對象
Promise.resolve = function (value) {
  return new Promise(function(resolve, reject){resolve(value)})
}
複製代碼
Promise.reject

返回一個rejected狀態的promise對象

// 返回一個rejected狀態的promise對象
Promise.reject = function (reason) {
  return new Promise(function(resolve, reject){reject(reason)})
}
複製代碼
Promise.all

接收一個promise對象數組爲參數 只有所有 promise 進入 fulfilled 狀態纔會繼續後面的處理 一般會用來處理 多個並行異步操做

/** * Promise.all Promise進行並行處理 * 參數: promise對象組成的數組做爲參數 * 返回值: 返回一個Promise實例 * 當這個數組裏的全部promise對象所有進入FulFilled狀態的時候,纔會resolve。 */
Promise.all = function (promises) {
  return new Promise((resolve, reject) => {
    let values = []
    let count = 0
    promises.forEach((promise, index) => {
      promise.then(value => {
        console.log('value:', value, 'index:', index)
        values[index] = value
        count++
        if (count === promises.length) {
          resolve(values)
        }
      }, reject)
    })
  })
}
複製代碼
Promise.race

接收一個promise對象數組爲參數 Promise.race 只要有一個promise對象進入 fulfilled 或者 rejected 狀態的話,就會繼續進行後面的處理

/** * Promise.race * 參數: 接收 promise對象組成的數組做爲參數 * 返回值: 返回一個Promise實例 * 只要有一個promise對象進入 FulFilled 或者 Rejected 狀態的話,就會繼續進行後面的處理(取決於哪個更快) */
Promise.race = function (promises) {
  return new Promise((resolve, reject) => {
      promises.forEach((promise) => {
         promise.then(resolve, reject);
      });
  });
}
複製代碼
至此,咱們就實現了一個的Promise,完整代碼以下:
function Promise (executor) {
  let self = this
  self.status = 'pending' //Promise當前狀態
  self.data = undefined //當前Promise的值
  self.onResolvedCallback = [] //Promise resolve時的回調函數集合
  self.onRejectedCallback = [] //Promise reject時的回調函數集合

  function resolve (value) { // value成功態時接收的終值
    if (value instanceof Promise) {
      value.then(resolve, reject)
      return
    }

    // 爲何resolve 加setTimeout?
    // 2.2.4規範 onFulfilled 和 onRejected 只容許在 execution context 棧僅包含平臺代碼時運行.
    // 注1 這裏的平臺代碼指的是引擎、環境以及 promise 的實施代碼。實踐中要確保 onFulfilled 和 onRejected 方法異步執行,且應該在 then 方法被調用的那一輪事件循環以後的新執行棧中執行。
    setTimeout(function(){
      // 調用resolve 回調對應onFulfilled函數
      if (self.status === 'pending') {
        // 只能由pending狀態 => fulfilled狀態 (避免調用屢次resolve reject)
        self.status = 'fulfilled'
        self.data = value
        //執行resolve的回調函數,將value傳遞到callback中
        self.onResolvedCallback.forEach(callback => callback(value))
      }
    })
  }

  function reject (reason) { // reason失敗態時接收的拒因
    setTimeout(function(){
      // 調用reject 回調對應onRejected函數
      if (self.status === 'pending') {
        // 只能由pending狀態 => rejected狀態 (避免調用屢次resolve reject)
        self.status = 'rejected'
        self.data = reason
        //執行reject的回調函數,將reason傳遞到callback中
        self.onRejectedCallback.forEach(callback => callback(reason))
      }
    })
  }

  try {
    executor(resolve, reject)
  } catch (e) {
    reject(e)
  }
  
}

/** * resolve中的值幾種狀況: * 1.普通值 * 2.promise對象 * 3.thenable對象/函數 */

/** * 對resolve 進行改造加強 針對resolve中不一樣值狀況 進行處理 * @param {promise} promise2 promise1.then方法返回的新的promise對象 * @param {[type]} x promise1中onFulfilled的返回值 * @param {[type]} resolve promise2的resolve方法 * @param {[type]} reject promise2的reject方法 */
function resolvePromise (promise2, x, resolve, reject) {

  let then 
  let thenCalledOrThrow = false // 避免屢次調用

  if (promise2 === x) { // 若是從onFulfilled中返回的x 就是promise2 就會致使循環引用報錯
    reject(new TypeError('Chaining cycle detected for promise!'))
    return
  }

  // 若是x是一個咱們本身寫的promise對象 
  if (x instanceof Promise) {
    if (x.status === 'pending') { // 若是爲等待態需等待直至 x 被執行或拒絕 並解析value值
      x.then(value => {
        resolvePromise(promise2, value, resolve, reject)
      }, err => {
        reject(err)
      })
    }  else { // 若是 x 已經處於執行態/拒絕態(值已經被解析爲普通值),用相同的值執行傳遞下去 promise
      x.then(resolve, reject)
    }
    return
  }

  // 若是 x 爲對象或者函數
  if ((x !== null) && ((typeof x === 'function') || (typeof x === 'object'))) {
    try {
      then = x.then //because x.then could be a getter
      if (typeof then === 'function') { // 是不是thenable對象(具備then方法的對象/函數)
        then.call(x, value => {
          if (thenCalledOrThrow) return
          thenCalledOrThrow = true
          resolvePromise(promise2, value, resolve, reject)
          return
        }, err => {
          if (thenCalledOrThrow) return
          thenCalledOrThrow = true
          reject(err)
          return
        })
      } else { // 說明是一個普通對象/函數
        resolve(x)
      }
    } catch (e) {
      if (thenCalledOrThrow) return
      thenCalledOrThrow = true
      reject(e)
      return
    }
  } else {
    resolve(x)
  }

}

/** * [註冊fulfilled狀態/rejected狀態對應的回調函數] * @param {function} onFulfilled fulfilled狀態時 執行的函數 * @param {function} onRejected rejected狀態時 執行的函數 * @return {function} newPromsie 返回一個新的promise對象 */
Promise.prototype.then = function (onResolve, onReject) {
  let self = this
  let promise2

  // 處理參數默認值 保證參數後續可以繼續執行
  onResolve = typeof onResolve==='function' ? onResolve : function(value){return value}
  onReject = typeof onReject==='function' ? onReject : function(reason){throw reason}
  
  if (self.status === 'pending') { // 等待態
     return promise2 = new Promise(function(resolve, reject){
      
      // 當異步調用resolve/rejected時 將onFulfilled/onRejected收集暫存到集合中
      self.onResolvedCallback.push(function(value){
        try {
          let x = onResolve(value)
          resolvePromise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      })

      self.onRejectedCallback.push(function(reason){
        try {
          let x = onReject(reason)
          resolvePromise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      })
    })
  }

  // then裏面的FULFILLED/REJECTED狀態時 爲何要加setTimeout ?
  // 緣由:
  // 其一 2.2.4規範 要確保 onFulfilled 和 onRejected 方法異步執行(且應該在 then 方法被調用的那一輪事件循環以後的新執行棧中執行) 因此要在resolve里加上setTimeout
  // 其二 2.2.6規範 對於一個promise,它的then方法能夠調用屢次.(當在其餘程序中屢次調用同一個promise的then時 因爲以前狀態已經爲FULFILLED/REJECTED狀態,則會走的下面邏輯),因此要確保爲FULFILLED/REJECTED狀態後 也要異步執行onFulfilled/onRejected

  // 其二 2.2.6規範 也是resolve函數里加setTimeout的緣由
  // 總之都是 讓then方法異步執行 也就是確保onFulfilled/onRejected異步執行

  // 以下面這種情景 屢次調用p1.then
  // p1.then((value) => { // 此時p1.status 由pending狀態 => fulfilled狀態
  // console.log(value); // resolve
  // // console.log(p1.status); // fulfilled
  // p1.then(value => { // 再次p1.then 這時已經爲fulfilled狀態 走的是fulfilled狀態判斷裏的邏輯 因此咱們也要確保判斷裏面onFuilled異步執行
  // console.log(value); // 'resolve'
  // });
  // console.log('當前執行棧中同步代碼');
  // })
  // console.log('全局執行棧中同步代碼');
  //
  if (self.status === 'fulfilled') { // 成功態
    return promise2 = new Promise(function(resolve, reject){
      setTimeout(function(){
        try {
          let x = onResolve(self.data)
          resolvePromise(promise2, x, resolve, reject) // 新的promise resolve 上一個onFulfilled的返回值
        } catch (e) {
          reject(e) // 捕獲前面onFulfilled中拋出的異常 then(onFulfilled, onRejected);
        }
      },0)
    })
  }

  if (self.status === 'rejected') { // 失敗態
    return promise2 = new Promise(function(resolve, reject){
      setTimeout(function(){
        try {
          let x = onReject(self.data)
          resolvePromise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      },0)
    })
  }
}

/** * Promise.all Promise進行並行處理 * 參數: promise對象組成的數組做爲參數 * 返回值: 返回一個Promise實例 * 當這個數組裏的全部promise對象所有進入FulFilled狀態的時候,纔會resolve。 */
Promise.all = function (promises) {
  return new Promise((resolve, reject) => {
    let values = []
    let count = 0
    promises.forEach((promise, index) => {
      promise.then(value => {
        console.log('value:', value, 'index:', index)
        values[index] = value
        count++
        if (count === promises.length) {
          resolve(values)
        }
      }, reject)
    })
  })
}

/** * Promise.race * 參數: 接收 promise對象組成的數組做爲參數 * 返回值: 返回一個Promise實例 * 只要有一個promise對象進入 FulFilled 或者 Rejected 狀態的話,就會繼續進行後面的處理(取決於哪個更快) */
Promise.race = function (promises) {
  return new Promise((resolve, reject) => {
      promises.forEach((promise) => {
         promise.then(resolve, reject);
      });
  });
}

// 用於promise方法鏈時 捕獲前面onFulfilled/onRejected拋出的異常
Promise.prototype.catch = function (onReject) {
  return this.then(null, onReject)
}

// 返回一個fulfilled狀態的promise對象
Promise.resolve = function (value) {
  return new Promise(function(resolve, reject){resolve(value)})
}

// 返回一個rejected狀態的promise對象
Promise.reject = function (reason) {
  return new Promise(function(resolve, reject){reject(reason)})
}

Promise.deferred = Promise.defer = function() {
  var defer = {}
  defer.promise = new Promise(function(resolve, reject) {
    defer.resolve = resolve
    defer.reject = reject
  })
  return defer
}

try {
  module.exports = Promise
} catch (e) {
}

// Promise核心內容完整測試方法
let promisesAplusTests =  require("promises-aplus-tests")
promisesAplusTests(Promise, function(err){
  console.log('err:', err);
  //所有完成;輸出在控制檯中。或者檢查`err`表示失敗次數。 
})
複製代碼

Tip: 完整代碼+測試demo文件地址

測試

如何肯定咱們實現的Promise符合標準呢?Promise有一個配套的測試腳本,只須要咱們在一個CommonJS的模塊中暴露一個deferred方法(即exports.deferred方法),就能夠了,代碼見上述代碼的最後。而後執行以下代碼便可執行測試:

npm i promises-aplus-tests
node ./Promise.js
複製代碼

相關知識參考資料

Promises/A+規範-英文

Promises/A+規範-翻譯-推薦

剖析Promise內部結構

相關文章
相關標籤/搜索