JS:手寫實現(xiàn) Promise

前言

JavaScript 中的 Promise 誕生于 ES2015(ES6),是當(dāng)下前端開發(fā)中特別流行的一種異步操作解決方案,也幾乎是前端面試過程中的必考題。如果能夠熟練運用 Promise,并深入理解 Promise 的運行原理,想必不論是在實際開發(fā)中還是在面試過程中,都能如魚得水。

本篇文章我們就來一步步分析 Promise 的特征與用法,并最終實現(xiàn)一個包含以下 API 的 Promise:

  • Promise.prototype.then()

  • Promise.prototype.catch()

  • Promise.prototype.finally()

  • Promise.all()

  • Promise.resolve()

  • Promise.reject()


TIPS:

具體實現(xiàn)步驟會在代碼中通過注釋說明。

為了避免代碼重復(fù),下面的每一步的實現(xiàn)過程都會忽略其他步驟的代碼,只包含當(dāng)前步驟的必要代碼。

完整代碼將會放在最后。


開始之前先回顧一下 Promise 的基本用法

  • 創(chuàng)建 Promise

    let asyncFunc = new Promise((resolve, rejcet) => {
      Math.random() > 0.5 ? resolve('fulfilled') : rejcet('rejected')
    })
    
  • 處理 Promise 返回的狀態(tài)(完成或者失敗)

    asyncFunc.then(
      res => {
        console.log(res)
      },
      err => {
        console.log(err)
      }
    )
    

第一步:聲明 Promise 的三種狀態(tài)

我們知道 Promise 有三種狀態(tài),他們不受外界影響,而且一旦狀態(tài)改變,就不會再變,任何時候都可以得到這個結(jié)果。這里先將它們枚舉出來,后續(xù)會大量用到:

  • 待定(pending): 初始狀態(tài),既沒有被兌現(xiàn),也沒有被拒絕。

    const PENDING = 'pending'
    
  • 已兌現(xiàn)(fulfilled): 意味著操作成功完成。

    const FULFILLED = 'fulfilled'
    
  • 已拒絕(rejected): 意味著操作失敗。

    const REJECTED = 'rejected'
    

第二步:實現(xiàn) reject() 和 resolve()

ES6 規(guī)定,Promise 對象是一個構(gòu)造函數(shù),用來生成 Promise 實例。它接受一個函數(shù)作為參數(shù),該函數(shù)的兩個參數(shù)分別是 resolvereject。它們也是函數(shù)。

// 以構(gòu)造函數(shù)的形式實現(xiàn)
class MyPromise {
  constructor(executor) {
    // 利用 try/catch 捕獲錯誤
    try {
      executor(this.resolve, this.reject)
    } catch (error) {
      this.reject(error)
    }
  }
  // 定義 Promise 初始狀態(tài)為 PENDING
  status = PENDING
  // resolve 后返回的數(shù)據(jù)
  data = undefined
  // reject 后返回的原因
  reason = undefined
  // 成功
  resolve = data => {
    // 一旦狀態(tài)改變,就不能再變
    if (this.status !== PENDING) return
    // 更改狀態(tài)
    this.status = FULFILLED
    // 保存數(shù)據(jù)
    this.data = data
  }
  // 失敗
  reject = reason => {
    // 一旦狀態(tài)改變,就不能再變
    if (this.status !== PENDING) return
    // 更改狀態(tài)
    this.status = REJECTED
    // 保存原因
    this.reason = reason
  }
}

第三步:實現(xiàn) .then()

.then() 方法是 Promise 的核心之一,異步操作的成功或失敗,都可以通過 .then() 添加的回調(diào)函數(shù)進(jìn)行處理。并且它將繼續(xù)返回一個 Promise 對象,這樣可以通過多次調(diào)用 .then() 添加多個回調(diào)函數(shù),它們會按照插入的順序執(zhí)行,形成鏈?zhǔn)秸{(diào)用(chaining)。

class MyPromise {
  // resolve 的回調(diào)函數(shù)列表
  successCallback = []
  // reject 的回調(diào)函數(shù)列表
  failureCallback = []
  // 成功
  resolve = data => {
    // 依次調(diào)用成功回調(diào)
    while (this.successCallback.length) {
      this.successCallback.shift()(this.data)
    }
  }
  // 失敗
  reject = reason => {
    // 依次調(diào)用失敗回調(diào)
    while (this.failureCallback.length) {
      this.failureCallback.shift()(this.reason)
    }
  }
  // .then():處理 resolve 和 reject
  then(onResolved = data => data /*設(shè)置默認(rèn)的成功回調(diào) */, onRejected) {
    // 創(chuàng)建一個新的 Promise 并 return,以供鏈?zhǔn)秸{(diào)用
    let promise = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        // 轉(zhuǎn)換為 異步執(zhí)行,用來獲取 新的 promise
        setTimeout(() => {
          try {
            let value = onResolved(this.data)
            // 判斷返回值是普通值還是 Promise
            resolvePromise(promise, value, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)
      } else if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            let value = onRejected(this.reason)
            resolvePromise(promise, value, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)
      } else {
        // 將回調(diào)函數(shù)存入數(shù)組中等待被執(zhí)行
        this.successCallback.push(() => {
          setTimeout(() => {
            try {
              let value = onResolved(this.data)
              resolvePromise(promise, value, resolve, reject)
            } catch (error) {
              reject(error)
            }
          }, 0)
        })
        // 將回調(diào)函數(shù)存入數(shù)組中等待被執(zhí)行
        this.failureCallback.push(() => {
          setTimeout(() => {
            try {
              let value = onRejected(this.reason)
              resolvePromise(promise, value, resolve, reject)
            } catch (error) {
              reject(error)
            }
          }, 0)
        })
      }
    })
    return promise
  }
}

第四步:實現(xiàn) .catch()

實現(xiàn) .then() 之后,.catch() 就會簡單很多。事實上,.catch() 只是沒有給 fulfilled 狀態(tài)預(yù)留參數(shù)位置的 .then() 而已,所以這里我們直接返回一個沒有成功回調(diào)函數(shù)的 .then() 即可。

class MyPromise {
  // .catch()
  catch(onRejected) {
    // 事實上 .catch() 只是沒有給 fulfilled 狀態(tài)預(yù)留參數(shù)位置的 .then()
    return this.then(undefined, onRejected)
  }
}

第五步:實現(xiàn) .finally()

有時候我們會在成功或失敗執(zhí)行相同的函數(shù),為了避免了同樣的語句需要在中各寫一次的情況,所以有了.finally() 方法。也就是說,在 Promise 的結(jié)果無論是 fulfilled 或者是 rejected,.finally() 都會執(zhí)行指定的回調(diào)函數(shù)。但區(qū)別于 .then().catch(),.finally() 的回調(diào)函數(shù)中不接收任何參數(shù)。

class MyPromise {
  // .finally()
  finally(callback) {
    return this.then(
      data => {
        return MyPromise.resolve(callback().then(() => data))
      },
      err => {
        return MyPromise.resolve(callback()).then(() => {
          throw err
        })
      }
    )
  }
}

第六步:實現(xiàn) Promise.all()

Promise.all() 方法主要用于集合多個 Promise 的返回結(jié)果。

class MyPromise {
  // Promise.all()
  static all(iterable) {
    // 記錄執(zhí)行次數(shù)
    let times = 0
    // 保存執(zhí)行結(jié)果
    let result = []
    // Promise.all() 會返回一個 Promise
    return new MyPromise((resolve, reject) => {
      // 記錄結(jié)果
      function addData(key, value) {
        times++
        result[key] = value
        times === iterable.length && resolve(result)
      }
      // 依次執(zhí)行,然后將結(jié)果保存到數(shù)組中
      iterable.forEach((element, index) => {
        // 判斷元素是否為 Promise 對象
        element instanceof MyPromise
          ? element.then(
              data => addData(index, data),
              err => reject(err) // 任何一個 Promise 對象的 reject 被執(zhí)行都會立即 reject()
            )
          : addData(index, element) // 非 promise 的元素將被直接放在返回數(shù)組中
      })
    })
  }
}

第七步:實現(xiàn) Promise.resolve()

Promise.resolve() 方法返回一個以給定值解析后的 Promise 對象。

class MyPromise {
  // Promise.resolve()
  static resolve(value) {
    // 返回一個以給定值解析后的 Promise 對象
    return value instanceof MyPromise
      ? value
      : new MyPromise(resolve => resolve(value))
  }
}

第八步:實現(xiàn) Promise.reject()

Promise.reject() 方法返回一個帶有拒絕原因的 Promise 對象。

class MyPromise {
  // Promise.reject()
  static reject(error) {
    return new MyPromise((resolve, reject) => {
      reject(error)
    })
  }
}

完整代碼

// 首先,我們聲明它的三種狀態(tài)
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

// 以構(gòu)造函數(shù)的形式實現(xiàn)
class MyPromise {
  constructor(executor) {
    // 利用 try/catch 捕獲錯誤
    try {
      executor(this.resolve, this.reject)
    } catch (error) {
      this.reject(error)
    }
  }
  // 定義 Promise 初始狀態(tài)為 PENDING
  status = PENDING
  // resolve 后返回的數(shù)據(jù)
  data = undefined
  // reject 后返回的原因
  reason = undefined
  // resolve 的回調(diào)函數(shù)列表
  successCallback = []
  // reject 的回調(diào)函數(shù)列表
  failureCallback = []
  // 成功
  resolve = data => {
    // 一旦狀態(tài)改變,就不能再變
    if (this.status !== PENDING) return
    // 更改狀態(tài)
    this.status = FULFILLED
    // 保存數(shù)據(jù)
    this.data = data
    // 依次調(diào)用成功回調(diào)
    while (this.successCallback.length) {
      this.successCallback.shift()(this.data)
    }
  }
  // 失敗
  reject = reason => {
    // 一旦狀態(tài)改變,就不能再變
    if (this.status !== PENDING) return
    // 更改狀態(tài)
    this.status = REJECTED
    // 保存原因
    this.reason = reason
    // 依次調(diào)用失敗回調(diào)
    while (this.failureCallback.length) {
      this.failureCallback.shift()(this.reason)
    }
  }
  // then:處理 resolve 和 reject
  then(onResolved = data => data /*設(shè)置默認(rèn)的成功回調(diào) */, onRejected) {
    // 創(chuàng)建一個新的 Promise 并 return,以供鏈?zhǔn)秸{(diào)用
    let promise = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        // 轉(zhuǎn)換為 異步執(zhí)行,用來獲取 新的 promise
        setTimeout(() => {
          try {
            let value = onResolved(this.data)
            // 判斷返回值是普通值還是 Promise
            resolvePromise(promise, value, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)
      } else if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            let value = onRejected(this.reason)
            resolvePromise(promise, value, resolve, reject)
          } catch (error) {
            reject(error)
          }
        }, 0)
      } else {
        // 將回調(diào)函數(shù)存入數(shù)組
        this.successCallback.push(() => {
          setTimeout(() => {
            try {
              let value = onResolved(this.data)
              resolvePromise(promise, value, resolve, reject)
            } catch (error) {
              reject(error)
            }
          }, 0)
        })
        // 將回調(diào)函數(shù)存入數(shù)組
        this.failureCallback.push(() => {
          setTimeout(() => {
            try {
              let value = onRejected(this.reason)
              resolvePromise(promise, value, resolve, reject)
            } catch (error) {
              reject(error)
            }
          }, 0)
        })
      }
    })
    return promise
  }
  // .catch()
  catch(onRejected) {
    // 事實上 .catch() 只是沒有給 fulfilled 狀態(tài)預(yù)留參數(shù)位置的 .then()
    return this.then(undefined, onRejected)
  }
  // .finally()
  finally(callback) {
    return this.then(
      data => {
        return MyPromise.resolve(callback().then(() => data))
      },
      err => {
        return MyPromise.resolve(callback()).then(() => {
          throw err
        })
      }
    )
  }
  // Promise.all()
  static all(iterable) {
    // 記錄執(zhí)行次數(shù)
    let times = 0
    // 保存執(zhí)行結(jié)果
    let result = []
    // Promise.all() 會返回一個 Promise
    return new MyPromise((resolve, reject) => {
      // 記錄結(jié)果
      function addData(key, value) {
        times++
        result[key] = value
        times === iterable.length && resolve(result)
      }
      // 依次執(zhí)行,然后將結(jié)果保存到數(shù)組中
      iterable.forEach((element, index) => {
        // 判斷元素是否為 Promise 對象
        element instanceof MyPromise
          ? element.then(
              data => addData(index, data),
              err => reject(err) // 任何一個 Promise 對象的 reject 被執(zhí)行都會立即 reject()
            )
          : addData(index, element) // 非 promise 的元素將被直接放在返回數(shù)組中
      })
    })
  }
  // Promise.resolve()
  static resolve(value) {
    // 返回一個以給定值解析后的 Promise 對象
    return value instanceof MyPromise
      ? value
      : new MyPromise(resolve => resolve(value))
  }
  // Promise.reject()
  static reject(error) {
    return new MyPromise((resolve, reject) => {
      reject(error)
    })
  }
}

// 判斷 Promise 的返回值類型
function resolvePromise(promise, value, resolve, reject) {
  // 循環(huán)調(diào)用報錯
  if (promise === value) {
    return reject(
      new TypeError('Chaining cycle detected for promise #<Promise>')
    )
  }
  // 如果是 Promise 對象
  if (value instanceof MyPromise) {
    value.then(resolve, reject)
  } else {
    resolve(value)
  }
}

module.exports = MyPromise
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容