簡單實現 Promises/A+ 規範

作者:@gauseen

Promises/A+ 規範可在這裏查看

promise3 個狀態,分別爲 pending, fulfilledrejected

  • promise 在 pending 狀態

    • 可以切換到 fulfilledrejected 狀態
  • promise 在 fulfilled 狀態

    • 不可以切換到其它狀態
    • 必須有個不可以更改的 value 值
  • promise 在 rejected 狀態

    • 不可以切換到其它狀態
    • 必須有個不可以更改的 reason 值
// promise 三種狀態
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

// MyPromise 構造函數
function MyPromise (fn) {
  // 初始化狀態
  this.state = PENDING
  this.result = void 0
  this.handlerQueue = []

  let resolve = (value) => {
    transitionState(this, FULFILLED, value)
  }

  let reject = (reason) => {
    transitionState(this, REJECTED, reason)
  }

  // 調用 Promise 構造函數回調
  try {
    fn(resolve, reject)
  } catch (error) {
    reject(error)
  }
}

狀態遷移方法,即調用了 fn(resolve, reject) 中的 resolve, reject 方法後,需要改變 promise 狀態:

pending --> fulfilled

pending --> rejected

function transitionState (promise, state, result) {
  if (promise.state !== PENDING) return
  promise.state = state
  promise.result = result

  // 這裏先佔個坑位
}

then 方法返回的是一個新的 Promise 實例(注意:不是原來那個 Promise 實例),只有這樣才能不斷的鏈式調用,依次改變狀態

MyPromise.prototype.then = function (onFulfilled, onRejected) {
  return new MyPromise((resolve, reject) => {
    let handler = { onFulfilled, onRejected, resolve, reject }
    // 若當前狀態爲 pending 則將其放在 handlerQueue 隊列中,等待 resolve 或 reject 方法改變其狀態
    // 否則直接調用 then 方法中的 resolve 或 reject 回調函數
    if (this.state ==== PENDING) {
      this.handlerQueue.push(handler)
    } else {
      dispatchHandler(handler, this.state, this.result)
    }
  })
}
const isFunction = arg => typeof arg === 'function'

function dispatchHandler (handler, state, result) {
  let { onFulfilled, onRejected, resolve, reject } = handler

  if (state === FULFILLED) {
    let finalValue = isFunction(onFulfilled) ? onFulfilled(result) : result
    resolve(finalValue)
  } else if (state === REJECTED) {
    let finalReason = isFunction(onRejected) ? onRejected(result) : result
    reject(finalReason)
  }
}

以上代碼,只支持 Promise 回調函數參數 resolvereject 同步調用的情況,如下示例代碼:

// 支持

let myPromise = new MyPromise((resolve, reject) => {
  // resolve 同步調用
  resolve('同步調用 value')
})

myPromise.then((value) => {
  console.log('value: ', value)
}, (reason) => {
  console.log('reason: ', reason)
})

但是,使用異步調用不支持,如下示例代碼:

// 暫不支持

let myPromise = new MyPromise((resolve, reject) => {
  // resolve 異步調用
  setTimeout(() => {
    resolve('異步調用 value')
  })
})

myPromise.then((value) => {
  console.log('value: ', value)
}, (reason) => {
  console.log('reason: ', reason)
})

之所以不支持異步調用 resolve 或 reject,是因爲 then 方法中如下代碼片段:

  // 當 resolve 爲異步調用,then 方法執行時,promise 狀態爲 pending。
  // 所以 then 回調函數 onFulfilled 和 onRejected 在 handlerQueue 隊列裏,沒有被調用
  if (this.state ==== PENDING) {
    this.handlerQueue.push(handler)
  } else {
    // ...
  }

爲支持 resolvereject 異步調用,狀態遷移方法 transitionState,做如下修改:

function transitionState (promise, state, result) {
  if (promise.state !== PENDING) return
  promise.state = state
  promise.result = result

  // 新增代碼開始
  promise.handlerQueue.forEach(handler => {
    dispatchHandler(handler, state, result)
  })
  // 新增代碼結束
}

因爲 catch 方法是 .then(null, onRejected) 的別名,所以實現 catch 代碼如下:

MyPromise.prototype.catch = function (onRejected) {
  return this.then(null, onRejected)
}

如上,簡單實現了 promise,支持鏈式調用 then 和 catch

歡迎關注無廣告文章公衆號:學前端

參考

promise-aplus-impl

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章