前端雜談: 如何實現一個 Promise?

首先, 什麼是 Promise?

A promise is an object that may produce a single value some time in the future: either a resolved value, or a reason that it’s not resolved (e.g., a network error occurred). A promise may be in one of 3 possible states: fulfilled, rejected, or pending. Promise users can attach callbacks to handle the fulfilled value or the reason for rejection.javascript

關鍵語句: Promise 是一個在未來某個時刻產生一個單一結果的對象. 通俗一點來講, Promise 表明了一個值, 可是這個值咱們並不肯定何時會被返回.前端

A promise is an object that may produce a single value some time in the future.java

簡單看看 Promise 的歷史

  • Promise 在 1980 年代被建立出來node

  • 在 1988 年正式得名: Promisegit

  • 已經有不少人瞭解到了 Promise, 可是人們仍是堅持使用 node.js 中提倡的以回調函數首個參數傳 error 對象的方式處理異步代碼.github

  • Dojo 首次大規模的使用了 Promise , 相應的 Promise/A 被提出用以規範 Promise 的實現api

  • JQuery 開始使用 Promise 並真正使 Promise 廣爲人知promise

  • JQuery 沒有實現部分 Promise 的功能, 這也致使了 Promie/A+ 標準的產生閉包

  • ES6 正式引入了 Promise,而且和已有的實現了 Promise/A 規範的 library 相兼容dom

實現 Promise 以前, 讓咱們看看 Promise 有哪些規範

  1. Promise 是一個 thenable 對象, 也就是說 Promise 有一個 .then() 方法
  2. 一個 pending 狀態的 Promise 能夠進入 fulfilled 和 rejected 狀態
  3. promise 一旦進入 fulfilled 或 rejected 狀態, 不可再改變其狀態
  4. 一旦 promise 改變了其狀態, 它筆芯有一個值(這個值也多是 undefined)

開始實現一個 Promise

首先, 讓咱們看看一段最普通的異步代碼:

// 異步方法定義
var basicAsyncFunc = function(callback) {
  setTimeout(function() {
    var randomNumber = Math.random()
    if (randomNumber > 0.5) {
      callback(null, randomNumber)
    } else {
      callback(new Error('bad luck...'))
    }
  }, 1000)
}

// 異步方法調用
basicAsyncFunc((err, result) => {
  if (err) {
    console.log(`the reason fail is: ${err}`)
    return
  }
  console.log(`success get result: ${result}`)
})
複製代碼

按照 Promise 的規範定義, 理想中 Promise 的調用方式爲:

// Promise 形式的異步方法定義
var promiseAsyncFunc = function() {}

// Promise 形式的異步方法調用
promiseAsyncFunc.then(
  data => {
    console.log(`success get result: ${data}`)
  },
  err => {
    console.log(`the reason fail is: ${err}`)
  }
)
複製代碼

按照這個理想當中的調用方式, 讓咱們寫出初版代碼.

初版 Promise:能保存回調方法

// Promise 形式的異步方法定義
var promiseAsyncFunc = function() {
  var fulfillCallback
  var rejectCallback

  setTimeout(() => {
    var randomNumber = Math.random()
    if (randomNumber > 0.5) fulfillCallback(randomNumber)
    else rejectCallback(randomNumber)
  }, 1000)
  return {
    then: function(_fulfillCallback, _rejectCallback) {
      fulfillCallback = _fulfillCallback
      rejectCallback = _rejectCallback
    }
  }
}

// Promise 形式的異步方法調用
promiseAsyncFunc().then(fulfillCallback, rejectCallback)
複製代碼

咱們的思路是在 .then() 方法中, 將 fullfill 和 reject 結果的回調函數保存下來, 而後在異步方法中調用. 由於是異步調用, 根據 event-loop 的原理, promiseAsyncFunc().then(fulfillCallback, rejectCallback) 傳入的 callback 在異步調用結束時必定是已經賦值過了.

第二版 Promise:實構造函數

當前咱們的實現 Promise 中,異步邏輯代碼和 Promise 的代碼是雜糅在一塊兒的,讓咱們將其區分開:

var promiseAsyncFunc = function() {
  var fulfillCallback
  var rejectCallback

  return {
    fulfill: function(value) {
      if (fulfillCallback && typeof fulfillCallback === 'function') {
        fulfillCallback(value)
      }
    },
    reject: function(err) {
      if (rejectCallback && typeof rejectCallback === 'function') {
        rejectCallback(err)
      }
    },
    then: function(_fulfillCallback, _rejectCallback) {
      fulfillCallback = _fulfillCallback
      rejectCallback = _rejectCallback
    }
  }
}

let ownPromise = function(asyncCall) {
  let promise = promiseAsyncFunc()
  asyncCall(promise.fulfill, promise.reject)
  return promise
}

// Promise 形式的異步方法調用
ownPromise(function(fulfill, reject) {
  setTimeout(() => {
    var randomNumber = Math.random()
    if (randomNumber > 0.5) fulfill(randomNumber)
    else reject(randomNumber)
  }, 1000)
})
複製代碼

咱們新定義了一個方法 ownPromise() 用於建立 Promise,並在promiseAsyncFunc() 中暴露出 fulfillreject 接口方便異步代碼去調用。

這裏有一個問題,咱們在調用 ownPromise()後獲得了 promise 實例,此時咱們能夠直接調用 fulfill()reject()這兩個方法,而理論上咱們應該只應暴露 promise 的then()方法。因此咱們利用閉包將這兩個方法隱藏:

var promiseAsyncFunc = function() {
  var fulfillCallback
  var rejectCallback

  return {
    fulfill: function(value) {
      if (fulfillCallback && typeof fulfillCallback === 'function') {
        fulfillCallback(value)
      }
    },
    reject: function(err) {
      if (rejectCallback && typeof rejectCallback === 'function') {
        rejectCallback(err)
      }
    },
    promise: {
      then: function(_fulfillCallback, _rejectCallback) {
        fulfillCallback = _fulfillCallback
        rejectCallback = _rejectCallback
      }
    }
  }
}

let ownPromise = function(asyncCall) {
  let defer = promiseAsyncFunc()
  asyncCall(defer.fulfill, defer.reject)
  return defer.promise
}

// Promise 形式的異步方法調用
ownPromise(function(fulfill, reject) {
  setTimeout(() => {
    var randomNumber = Math.random()
    if (randomNumber > 0.5) fulfill(randomNumber)
    else reject(randomNumber)
  }, 1000)
})
複製代碼

第三版 Promise: 支持狀態管理

爲了實現規範中對於 Promise 狀態變化的要求, 咱們須要爲 Promise 加入狀態管理, 這一步較爲簡單, 讓咱們看代碼:

const PENDING = Symbol('pending')
const FULFILLED = Symbol('fulfilled')
const REJECTED = Symbol('rejected')

// Promise 形式的異步方法定義
var promiseAsyncFunc = function() {
  var status = PENDING
  var fulfillCallback
  var rejectCallback

  return {
    fulfill: function(value) {
      if (status !== PENDING) return
      if (typeof fulfillCallback === 'function') {
        fulfillCallback(value)
        status = FULFILLED
      }
    },
    reject(error) {
      if (status !== PENDING) return
      if (typeof rejectCallback === 'function') {
        rejectCallback(error)
        status = REJECTED
      }
    },
    promise: {
      then: function(_fulfillCallback, _rejectCallback) {
        fulfillCallback = _fulfillCallback
        rejectCallback = _rejectCallback
      }
    }
  }
}

let ownPromise = function(asyncCall) {
  let defer = promiseAsyncFunc()
  asyncCall(defer.fulfill, defer.reject)
  return defer.promise
}

// Promise 形式的異步方法調用
ownPromise(function(fulfill, reject) {
  setTimeout(() => {
    var randomNumber = Math.random()
    if (randomNumber > 0.5) fulfill(randomNumber)
    else reject(randomNumber)
  }, 1000)
}).then(data => console.log(data), err => console.log(err))
複製代碼

這段代碼中咱們用到了 Symbol 來表示狀態常量, 對 Symbol 不瞭解的同窗能夠看這裏

爲了判斷 Promise 的狀態, 咱們加入了 fulfillreject 兩個方法。並在其中判斷 promise 當前狀態。若是不是 pending 狀態則直接 return(由於 Promise 狀態只可能改變一次)。

如今咱們的 promise 實現了對狀態控制的規範:

  • 只容許改變一次狀態
  • 只能從 pending => fulfilled 或 pending => rejected

可是咱們的 Promise 有一個問題: promise 的值沒有被保存下來。若是 promise 在異步調用完成以後才被調用 .then() 方法,則咱們沒法把異步調用的結果傳遞給回調函數。爲此咱們須要爲 Promise 加一個 value 字段:

第四版 Promise: 保存異步調用的結果

咱們爲 promise 加入 value 字段,用於保存 Promise 的執行結果。

// Promise 形式的異步方法定義
var promiseAsyncFunc = function() {
  var status = PENDING
  var fulfillCallback
  var rejectCallback
  var value

  return {
    fulfill: function(_value) {
      if (status !== PENDING) return
      value = _value
      status = FULFILLED
      if (typeof fulfillCallback === 'function') {
        fulfillCallback(value)
      }
    },
    reject(error) {
      if (status !== PENDING) return
      value = error
      status = REJECTED
      if (typeof rejectCallback === 'function') {
        rejectCallback(error)
      }
    },
    promise: {
      then: function(_fulfillCallback, _rejectCallback) {
        fulfillCallback = _fulfillCallback
        rejectCallback = _rejectCallback
      }
    }
  }
}
複製代碼

這裏咱們又發現一個問題,若是一個 Promise 已是fulfillreject狀態。咱們再調用 then() 方法時,傳入的回調方法永遠不會被調用(由於 status 已經不是 pending)。

因此咱們須要在 then()方法中對其狀態進行判斷:

// Promise 形式的異步方法定義
var promiseAsyncFunc = function() {
  var status = PENDING
  var fulfillCallback
  var rejectCallback
  var value

  return {
    fulfill: function(_value) {
      if (status !== PENDING) return
      value = _value
      status = FULFILLED
      if (typeof fulfillCallback === 'function') {
        fulfillCallback(value)
      }
    },
    reject(error) {
      if (status !== PENDING) return
      value = error
      status = REJECTED
      if (typeof rejectCallback === 'function') {
        rejectCallback(error)
      }
    },
    promise: {
      then: function(_fulfillCallback, _rejectCallback) {
        if (status === REJECTED) {
          _rejectCallback(value)
          return
        }
        if (status === FULFILLED) {
          _fulfillCallback(value)
          return
        }
        fulfillCallback = _fulfillCallback
        rejectCallback = _rejectCallback
      }
    }
  }
}
複製代碼

第五版 Promise: 支持鏈式調用

爲了支持鏈式調用,.then() 方法的返回值必須是用 thenable (根據 Promise/A+ 規範, .then() 方法的返回值須要是一個的 Promise)

爲此咱們加入一個工具方法 makeThenable()。若是傳入的 value 自己就有 then()方法,則直接返回 value。不然返回一個有 then()方法的對象。 在該對象的 then()方法中,咱們根據 promise 的狀態,調用不一樣的回調方法生成新的 value。

function makeThenable(value, status){
  if(value && typeof value.then === 'function'){
    return value
  }
  if(status === FULFILLED){
    return {
      then: function(fulfillCallback, rejectCallback){
        return makeThenable(fulfillCallback(value), FULFILLED)
      }
    }
  }
  if(status === REJECTED) {
    return {
      then: function(fulfillCallback, rejectCallback){
        return makeThenable(rejectCallback(value), FULFILLED)
      }
    }
  }
}
複製代碼

有了以上的 makeThenable()方法,咱們能夠在 promise 的fulfill()reject()回將 value 設置爲 thenable:

var promiseAsyncFunc = function() {
  var status = PENDING
  var fulfillCallback
  var rejectCallback
  var value

  return {
    fulfill: function(_value) {
      if (status !== PENDING) return
      value = makeThenable(_value, FULFILLED) // 保證當前promise的value爲 thenable
      status = FULFILLED
      if (typeof fulfillCallback === 'function') {
        value.then(fulfillCallback)
      }
    },
    reject(error) {
      if (status !== PENDING) return
      value = makeThenable(error, REJECTED) 、、  // 保證當前value爲 thenable
      status = REJECTED
      if (typeof rejectCallback === 'function') {
        value.then(null, rejectCallback)
      }
    },
    promise: {
      then: function(){}
    }
  }
}
複製代碼

接下來讓咱們看 then()方法。爲了返回一個新的 promise,咱們首先得建立一個新的 promise。其次當前 promise 在fulfill()reject()時,應該調用新的 promise 的fullfill()reject()方法。因此咱們在將 fulfullCallbackrejectCallback賦值給當前 promise 時,將其包裝一下。代碼以下:

promise: {
      then: function(_fulfillCallback, _rejectCallback) {
        let newPromiseAsyncFunc = promiseAsyncFunc()
        let fulfillFunc = function(value) {
          newPromiseAsyncFunc.fulfill(_fulfillCallback(value))
        }
        let rejectFunc = function(err) {
          newPromiseAsyncFunc.fulfill(_rejectCallback(err))
        }
        if (status === PENDING) {
          fulfillCallback = fulfillFunc
          rejectCallback = rejectFunc
        } else {
          value.then(fulfillFunc, rejectFunc)
        }
        return newPromiseAsyncFunc.promise
      }
    }
複製代碼

如此,咱們變獲得了一個能夠鏈式調用的 promise。讓咱們來測試一下:

const PENDING = Symbol('pending')
const FULFILLED = Symbol('fulfilled')
const REJECTED = Symbol('rejected')

function makeThenable(value, status) {
  if (value && typeof value.then === 'function') {
    return value
  }
  if (status === FULFILLED) {
    return {
      then: function(fulfillCallback, rejectCallback) {
        return makeThenable(fulfillCallback(value), FULFILLED)
      }
    }
  }
  if (status === REJECTED) {
    return {
      then: function(fulfillCallback, rejectCallback) {
        return makeThenable(rejectCallback(value), FULFILLED)
      }
    }
  }
}

// Promise 形式的異步方法定義
var promiseAsyncFunc = function() {
  var status = PENDING
  var fulfillCallback
  var rejectCallback
  var value

  return {
    fulfill: function(_value) {
      if (status !== PENDING) return
      value = makeThenable(_value, FULFILLED)
      status = FULFILLED
      if (typeof fulfillCallback === 'function') {
        value.then(fulfillCallback)
      }
    },
    reject(error) {
      if (status !== PENDING) return
      value = makeThenable(error, REJECTED)
      status = REJECTED
      if (typeof rejectCallback === 'function') {
        value.then(null, rejectCallback)
      }
    },
    promise: {
      then: function(_fulfillCallback, _rejectCallback) {
        let newPromiseAsyncFunc = promiseAsyncFunc()
        let fulfillFunc = function(value) {
          newPromiseAsyncFunc.fulfill(_fulfillCallback(value))
        }
        let rejectFunc = function(err) {
          newPromiseAsyncFunc.fulfill(_rejectCallback(err))
        }
        if (status === PENDING) {
          fulfillCallback = fulfillFunc
          rejectCallback = rejectFunc
        } else {
          value.then(fulfillFunc, rejectFunc)
        }
        return newPromiseAsyncFunc.promise
      }
    }
  }
}

let ownPromise = function(asyncCall) {
  let defer = promiseAsyncFunc()
  asyncCall(defer.fulfill, defer.reject)
  return defer.promise
}

let testChainedPromise = ownPromise(function(fulfill, reject) {
  setTimeout(() => {
    var randomNumber = Math.random()
    if (randomNumber > 0.5) fulfill(randomNumber)
    else reject(randomNumber)
  }, 1000)
})
  .then(
    data => {
      console.log(data)
      return 'return value in then1 fulfill'
    },
    err => {
      console.log(err)
      return 'return value in then1 reject'
    }
  )
  .then(
    data => {
      console.log(data)
      return 'return value in then2 fulfill'
    },
    err => {
      console.log(err)
      return 'return value in then2 reject'
    }
  )
  .then(
    data => {
      console.log(data)
    },
    err => {
      console.log(err)
    }
  )

/** console output: 0.9931984611850693 return value in then1 fulfill return value in then2 fulfill */
複製代碼

第六版 Promise: Error handling

這裏咱們只對異步調用fulfill 回調中拋出的 error 進行處理。

首先是異步調用部分,咱們將其 try catch 起來,在發生異常時調用 reject 方法,並將異常做爲參數傳入。

let ownPromise = function(asyncCall) {
  let defer = promiseAsyncFunc()
  try {
    asyncCall(defer.fulfill, defer.reject)
  } catch (e) {
    defer.reject(e)
  }
  return defer.promise
}
複製代碼

而後是 fulfill 中可能出現的異常。咱們對fulfillCallback(value)可能出現的異常進行捕獲,並將異常傳遞給rejectCallback

function makeThenable(value, status) {
  if (value && typeof value.then === 'function') {
    return value
  }
  if (status === FULFILLED) {
    return {
      then: function(fulfillCallback, rejectCallback) {
        try {
          let newValue = fulfillCallback(value)
          return makeThenable(newValue, FULFILLED)
        } catch (e) {
          return makeThenable(rejectCallback(e), FULFILLED)
        }
      }
    }
  }
  if (status === REJECTED) {
    return {
      then: function(fulfillCallback, rejectCallback) {
        return makeThenable(rejectCallback(value), FULFILLED)
      }
    }
  }
}
複製代碼

最後讓咱們對完整的代碼進行測試:

const PENDING = Symbol('pending')
const FULFILLED = Symbol('fulfilled')
const REJECTED = Symbol('rejected')

function makeThenable(value, status) {
  if (value && typeof value.then === 'function') {
    return value
  }
  if (status === FULFILLED) {
    return {
      then: function(fulfillCallback, rejectCallback) {
        try {
          let newValue = fulfillCallback(value)
          return makeThenable(newValue, FULFILLED)
        } catch (e) {
          return makeThenable(rejectCallback(e), FULFILLED)
        }
      }
    }
  }
  if (status === REJECTED) {
    return {
      then: function(fulfillCallback, rejectCallback) {
        return makeThenable(rejectCallback(value), FULFILLED)
      }
    }
  }
}

// Promise 形式的異步方法定義
var promiseAsyncFunc = function() {
  var status = PENDING
  var fulfillCallback
  var rejectCallback
  var value

  return {
    fulfill: function(_value) {
      if (status !== PENDING) return
      value = makeThenable(_value, FULFILLED)
      status = FULFILLED
      if (typeof fulfillCallback === 'function') {
        value.then(fulfillCallback)
      }
    },
    reject(error) {
      if (status !== PENDING) return
      value = makeThenable(error, REJECTED)
      if (typeof rejectCallback === 'function') {
        value.then(null, rejectCallback)
      }
      status = REJECTED
    },
    promise: {
      then: function(_fulfillCallback, _rejectCallback) {
        let newPromiseAsyncFunc = promiseAsyncFunc()
        let fulfillFunc = function(value) {
          newPromiseAsyncFunc.fulfill(_fulfillCallback(value))
        }
        let rejectFunc = function(err) {
          newPromiseAsyncFunc.fulfill(_rejectCallback(err))
        }
        if (status === PENDING) {
          fulfillCallback = fulfillFunc
          rejectCallback = rejectFunc
        } else {
          value.then(fulfillFunc, rejectFunc)
        }
        return newPromiseAsyncFunc.promise
      }
    }
  }
}

let ownPromise = function(asyncCall) {
  let defer = promiseAsyncFunc()
  try {
    asyncCall(defer.fulfill, defer.reject)
  } catch (e) {
    defer.reject(e)
  }
  return defer.promise
}

let testChainedPromise = ownPromise(function(fulfill, reject) {
  throw Error('here is an error in asyncCall')
  setTimeout(() => {
    var randomNumber = Math.random()
    if (randomNumber > 0.5) fulfill(randomNumber)
    else reject(randomNumber)
  }, 1000)
})
  .then(
    data => {
      console.log(data)
      return 'return value in then1 fulfill'
    },
    err => {
      console.log(err.message)
      return 'return value in then1 reject'
    }
  )
  .then(
    data => {
      console.log(data)
      throw Error('here is an error in fulfill1')
      return 'return value in then2 fulfill'
    },
    err => {
      console.log(err.message)
      return 'return value in then2 reject'
    }
  )
  .then(
    data => {
      console.log(data)
    },
    err => {
      console.log(err.message)
    }
  )


// console out:
Error: here is an error in asyncCall
return value in then1 reject
Error: here is an error in fulfill1
return value in then2 reject
複製代碼

總結

以上就是咱們對於 Promise 的一個簡單的實現,實現思路主要參考了 Q-A promise library for javascript。該實現的 Promise 功能較爲簡陋,僅實現了部分 api/規範。有任何意見和建議歡迎在評論區交流 ;)

進一步閱讀 && 引用

對於 Promise 使用以及error handle 的講解:

medium.com/javascript-…

Promise 實現庫之一: Q 對於 Promise 實現的講解:

github.com/kriskowal/q…

想了解更多 前端 和 數據可視化 ?

這裏是個人 前端D3.js數據可視化 的 github 地址, 歡迎 star & fork :tada:

ssthouse-blog

若是以爲本文不錯的話, 不妨點擊下面的連接關注一下 : )

github 主頁

知乎專欄

掘金

想直接聯繫我 ?

郵箱: ssthouse@163.com

歡迎關注個人公衆號:

相關文章
相關標籤/搜索