1. 程式人生 > 其它 >模擬實現Promise,探究Promise原理

模擬實現Promise,探究Promise原理

promise的功能

建構函式的實現

首先寫一段promise的程式碼來分析一下建構函式的實現:

let pro = new Promise((resolve, reject) => {
  if (true) {
    resolve('the value')
  } else {
    reject('the reason')
  }
})

promise一共有pending(進行中)、fulfilled(已成功)、rejected(已失敗)三種狀態,一旦狀態確定就不可更改。初始狀態為pending,而且狀態變更只有兩種:

pending --> fulfilled

pending --> rejected

promise的建構函式,傳了一個函式作為引數,這個函式接受promise返回的兩個函式:resolve、reject,在建構函式中呼叫,呼叫後改變狀態。

便可以寫出如下建構函式:

const PENDING = 'pending' // 進行中
const FULFILLED = 'fulfilled' // 已成功
const REJECTED = 'rejected' // 已失敗

class MyPromise {
  constructor(executor) {
    executor(this.resolve, this.reject)
  }
  // 初始狀態PENDING
  status = PENDING

  resolve() {
    // 只有PENDING狀態能變成FULFILLED狀態
    if (this.status !== PENDING) return
    // 狀態改變為成功
    this.status = FULFILLED
  }
  reject() {
    // 只有PENDING狀態能變成REJECTED狀態
    if (this.status !== PENDING) 
    // 狀態變為失敗
    this.status = REJECTED
  }
}

then方法的實現

呼叫一下上述寫的promise

pro.then(res => {
    console.log(res)
}, (reason) => {
    console.log(reason)
})

promise原型上有一個then方法。then方法接受成功回撥和失敗回撥兩個引數。then方法內部判斷狀態, 如果是成功就呼叫成功回撥函式,如果是失敗就呼叫失敗回撥函式。

建構函式如果呼叫resolve會傳遞一個值,作為then方法中成功回撥的引數;如果呼叫reject會傳遞一個值,作為then方法失敗回撥的引數。

便可以寫出如下程式碼:

const PENDING = 'pending' // 進行中
const FULFILLED = 'fulfilled' // 已成功
const REJECTED = 'rejected' // 已失敗

class MyPromise {
  constructor(executor) {
    executor(this.resolve, this.reject)
  }
  // 初始狀態PENDING
  status = PENDING
  // 成功之後的值
  value = undefined
  // 失敗之後的原因
  reason = undefined

  resolve = (value) => {
    // 只有PENDING狀態能變成FULFILLED狀態
    if (this.status !== PENDING) return
    // 狀態改變為成功
    this.status = FULFILLED

    this.value = value
  }
  reject = (reason) => {
    // 只有PENDING狀態能變成REJECTED狀態
    if (this.status !== PENDING) 
    // 狀態變為失敗
    this.status = REJECTED

    this.reason = reason
  }
  then = (successCallback, failCallback) => {
    if (this.status === FULFILLED) {
      successCallback(this.value)
    } else {
      failCallback(this.reason)
    }
  }
}

tips: 每一步可以自行呼叫,驗證是否已實現相關功能。

完善then支援非同步

很明顯我們的方法是不支援非同步的,接下來實現非同步功能:

let pro = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(100)
  }, 1000)
})

pro.then((res) => {
  console.log(res)
})

很明顯,我們需要在上述setTimeout內部執行的時候呼叫resolve,去改變狀態。那then方法就會先呼叫,所以這時候我們應該在then的時候增加一種pending狀態判斷,這時候要把then傳入的函式快取起來。等到resolve呼叫的時候再去執行。實現如下:

 // 快取成功回撥函式
  successCallback = null
  // 快取失敗回撥函式
  failCallback = null

  resolve = (value) => {
    // 只有PENDING狀態能變成FULFILLED狀態
    if (this.status !== PENDING) return
    // 狀態改變為成功
    this.status = FULFILLED

    this.value = value
    // 執行成功回撥
    if (this.successCallback) this.successCallback(this.value)
  }
  reject = (reason) => {
    // 只有PENDING狀態能變成REJECTED狀態
    if (this.status !== PENDING) 
    // 狀態變為失敗
    this.status = REJECTED

    this.reason = reason
    // 執行失敗回撥
    if (this.failCallback) this.failCallback(this.reason)
  }
  then = (successCallback, failCallback) => {
    if (this.status === FULFILLED) {
      successCallback(this.value)
    } else if (this.status === REJECTED) {
      failCallback(this.reason)
    } else {
       /**
       * pending狀態 暫存成功回撥和失敗回撥
       * 非同步情況處理
       * **/
      this.successCallback = successCallback
      this.failCallback = failCallback
    }
  }

鏈式呼叫

promise是支援鏈式呼叫的,並且上一步的返回值會作為下一步的引數傳入:

pro.then((res) => {
  console.log(res) // 100
  return 1000
}).then(res => {
  console.log(res) // 1000
  return 10000
}).then(res => {
  console.log(res) // 10000
}) 
//
pro.then().then((res) => console.log(res)) // 100

要實現鏈式呼叫,那then呼叫的時候還是要返回promise物件,因為then方法在promise上, 暫且將返回的promise物件叫做promise2,then方法就叫做then2。

let promise2 = new MyPromise((resolve, reject) => {})

這個返回的promise2呼叫resolve、reject的時機應該根據then2傳入的successCallback、failCallback來決定。

這裡要分為成功、失敗和非同步兩種情況

成功、失敗狀態:

判斷result的值是普通值還是promise物件:

如果是普通值 直接呼叫resolve

如果是promise物件 檢視promise物件返回的結果,再根據promise物件返回的結果 決定呼叫resolve還是呼叫reject

非同步:

暫存成功回撥和失敗回撥

這時候我們要把多次呼叫的回撥函式快取起來,快取到陣列,resolve呼叫時,再逐一呼叫快取的回撥

當鏈式呼叫沒有傳遞任何引數時,要向下傳遞值,所以我們在then內部,開始的時候判斷, 如果沒有成功或者失敗回撥,要直接返回值、或者失敗原因:

/**
 * then不傳遞引數的時候不引數
 * 逐級傳遞引數
 ***/ 
 successCallback = successCallback ? successCallback:value => value
 failCallback = failCallback ? failCallback:reason => { throw reason }

具體實現如下:

  // 快取成功回撥函式
  successCallback = []
  // 快取失敗回撥函式
  failCallback = []

  resolve = (value) => {
    // 只有PENDING狀態能變成FULFILLED狀態
    if (this.status !== PENDING) return
    // 狀態改變為成功
    this.status = FULFILLED

    this.value = value
    if (this.successCallback.length) this.successCallback.shift()(this.value)
  }
  reject = (reason) => {
    // 只有PENDING狀態能變成REJECTED狀態
    if (this.status !== PENDING) 
    // 狀態變為失敗
    this.status = REJECTED

    this.reason = reason

    if (this.failCallback.length) this.failCallback.shift()(this.reason)
  }
  then = (successCallback, failCallback) => {
    /**
     * then不傳遞引數的時候不引數
     * 逐級傳遞引數
     * **/ 
    successCallback = successCallback ? successCallback:value => value
    failCallback = failCallback ? failCallback:reason => { throw reason }
    const promise2 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        /**
         * 判斷result的值是普通值還是promise物件:
         * 如果是普通值 直接呼叫resolve
         * 如果是promise物件 檢視promise物件返回的結果
         * 再根據promise物件返回的結果 決定呼叫resolve還是呼叫reject
         * **/
        let result = successCallback(this.value)
        checkPromise(result, resolve, reject)
      } else if (this.status === REJECTED) {
        let result = failCallback(this.reason)
        checkPromise(result, resolve, reject)
      } else {
        /**
         * pending狀態 暫存成功回撥和失敗回撥
         * 非同步情況處理
         * **/
        this.successCallback.push(() => {
          /**
           * 判斷result的值是普通值還是promise物件:
           * 如果是普通值 直接呼叫resolve
           * 如果是promise物件 檢視promise物件返回的結果
           * 再根據promise物件返回的結果 決定呼叫resolve還是呼叫reject
           * **/
          let result = successCallback(this.value)
          checkPromise(result, resolve, reject)
        })
        this.failCallback.push(() => {
          /**
           * 判斷result的值是普通值還是promise物件:
           * 如果是普通值 直接呼叫resolve
           * 如果是promise物件 檢視promise物件返回的結果
           * 再根據promise物件返回的結果 決定呼叫resolve還是呼叫reject
           * **/
          let result = failCallback(this.value)
          checkPromise(result, resolve, reject)
        })
      }
    })
    return promise2
  }

到這裡 我們已經實現了promise的核心方法,下面再來補充promise 的一些呼叫方法。

catch實現

首先,為了程式碼的健壯性,我們要try-catch異常捕獲。

在建構函式的時候,當捕獲到異常要直接呼叫reject方法。

try {
   executor(this.resolve, this.reject)
} catch (error) {
   this.reject(error)
}

http://www.bijianshuo.com 軟文發稿平臺

在then方法中, 呼叫成功或失敗回撥的時候,如果捕獲到異常,就直接走reject方法了。

try {
	let result = successCallback(this.value)
	checkPromise(result, resolve, reject)
} catch(error) {
	reject(error)
}

觀察一下:

  pro.then()
     .catch(e => {
         
     })

catch 是promise原型上的,方法接收一個失敗回撥,實現如下:

 catch = (failCallback) => {
    this.then(null, failCallback)
  }