手寫 Promise

地靈 發表於 2022-05-20

就是幹

ECMAscript 6 原生提供了 Promise 物件。Promise 物件代表了未來將要發生的事件,用來傳遞非同步操作的訊息。
接下來用分塊、分步驟、加註釋來一步一步,實現手寫 Promise。

一、實現 Promise 的基本使用

  1. Promise 就是一個類在執行這個類的時候需要傳遞一個執行器進去,執行器會立即執行
  2. Promise 中有三種狀態 分別為 等待(pending) 成功(fulfilled)失敗(rejected)
    一旦狀態確定就不可更改
    pending -> fulfilled
    pending -> rejected
  3. resolve 和 reject 函式是用來更改狀態的
    resolve: fulfilled
    reject: rejected
  4. then 方法內部做的事情就判斷狀態,如果狀態成功就呼叫成功的回撥函式,如果狀態失敗就呼叫失敗的回撥函式 then 方法是被定義在原型物件上面
  5. then 成功和失敗之後都會有一個對應的成功值和失敗值
    先定義三個常量分別代表以下狀態
  • const PENDING = 'pending' // 等待
  • const FULFILLED = 'fulfilled' // 成功
  • const REJECTED = 'rejected' // 失敗
簡單講了一下基本概念,下面貼程式碼:
class MyPromise {
  constructor(executor) {
    executor(this.resolve, this.reject)
  }
  status = PENDING // 預設值 等待
  value = undefined // 成功之後的值
  reason = undefined // 失敗後的值
  // resolve,reject 箭頭函式 是為了讓這個函式內部 this 指向這個類的例項物件,也就是 promise
  resolve = (value) => {
    // 如果狀態不是等待 阻止程式繼續執行
    if (this.status !== PENDING) return
    // 將狀態改為成功
    this.status = FULFILLED
    // 儲存成功之後的值
    this.value = value
  }
  reject = (reason) => {
    // 如果狀態不是等待 阻止程式繼續執行
    if (this.status !== PENDING) return
    // 將狀態改為成功
    this.status = REJECTED
    // 儲存失敗後的原因
    this.reason = reason
  }
  then(successCallback, failCallback) {
    // 判斷狀態
    if (this.status === FULFILLED) {
      successCallback(this.value)
    } else if (this.status === REJECTED) {
      failCallback(this.reason)
    }
  }
}

二、實現 Promise 的非同步情況

  1. 處理非同步情況 和多個 promise.then 這裡指的是多個 promise 的使用,並不是鏈式呼叫
class MyPromise {
  constructor(executor) {
    executor(this.resolve, this.reject)
  }
  status = PENDING // 預設值 等待
  value = undefined // 成功之後的值
  reason = undefined // 失敗後的值
  successCallback = [] // 成功回撥,儲存多個函式,鏈式呼叫
  failCallback = [] // 失敗回撥,儲存多個函式,鏈式呼叫
  // resolve,reject 箭頭函式 是為了讓這個函式內部 this 指向這個類的例項物件,也就是 promise
  resolve = (value) => {
    // 如果狀態不是等待 阻止程式繼續執行
    if (this.status !== PENDING) return
    // 將狀態改為成功
    this.status = FULFILLED
    // 儲存成功之後的值
    this.value = value
    // 迴圈儲存的成功回撥函式長度,然後依次執行
    while (this.successCallback.length) this.successCallback.shift()(this.value)
  }
  reject = (reason) => {
    // 如果狀態不是等待 阻止程式繼續執行
    if (this.status !== PENDING) return
    // 將狀態改為成功
    this.status = REJECTED
    // 儲存失敗後的原因
    this.reason = reason
    // 迴圈儲存的失敗回撥函式長度,然後依次執行
    while (this.failCallback.length) this.failCallback.shift()(this.reason)
  }
  then(successCallback, failCallback) {
    // 判斷狀態
    if (this.status === FULFILLED) {
      successCallback(this.value)
    } else if (this.status === REJECTED) {
      failCallback(this.reason)
    } else {
      // 因為等待期間你不知道後面執行的是成功的回撥還是失敗的回撥
      // 所以將成功回撥和失敗回撥儲存起來
      this.successCallback.push(successCallback)
      this.failCallback.push(failCallback)
    }
  }
}

三、實現 Promise 的鏈式呼叫及捕獲錯誤資訊

  1. then 方法的鏈式呼叫,以及避免 promise 物件自已返回自己 形成迴圈
  2. 將 then 引數變成可選引數
  3. 捕獲執行器錯誤資訊,以及使用 then 時捕獲錯誤資訊
class MyPromise {
  constructor(executor) {
    try {
      executor(this.resolve, this.reject)
    } catch (e) {
      this.reject(e)
    }
  }
  status = PENDING // 預設值 等待
  value = undefined // 成功之後的值
  reason = undefined // 失敗後的值
  successCallback = [] // 成功回撥,儲存多個函式,鏈式呼叫
  failCallback = [] // 失敗回撥,儲存多個函式,鏈式呼叫
  // resolve,reject 箭頭函式 是為了讓這個函式內部 this 指向這個類的例項物件,也就是 promise
  resolve = (value) => {
    // 如果狀態不是等待 阻止程式繼續執行
    if (this.status !== PENDING) return
    // 將狀態改為成功
    this.status = FULFILLED
    // 儲存成功之後的值
    this.value = value
    // 迴圈儲存的成功回撥函式長度,然後依次執行
    while (this.successCallback.length) this.successCallback.shift()()
  }
  reject = (reason) => {
    // 如果狀態不是等待 阻止程式繼續執行
    if (this.status !== PENDING) return
    // 將狀態改為成功
    this.status = REJECTED
    // 儲存失敗後的原因
    this.reason = reason
    // 迴圈儲存的失敗回撥函式長度,然後依次執行
    while (this.failCallback.length) this.failCallback.shift()()
  }
  then(successCallback, failCallback) {
    // 判斷,變成可選引數,假設第一個,第二個 .then 不傳引數,第三個傳參,一樣能拿到資料 
    successCallback = successCallback ? successCallback : value => value
    failCallback = failCallback ? failCallback : reason => { throw reason }
    // 為了鏈式呼叫返回一個新的 promise 物件
    let newPromise = new MyPromise((resolve, reject) => {
      // 判斷狀態
      if (this.status === FULFILLED) {
        // 這裡用計時器的原因是為了非同步呼叫,因為需要等 newPromise 執行完畢後,才可以把他當引數傳遞給 resolvePromise 這個函式
        setTimeout(() => {
          try {
            let x = successCallback(this.value)
            // 儲存上一個成功回撥的返回值,傳遞給下一個 .then
            // 判斷 x 的值是普通值還是 promise 物件
            // 如果是普通值,直接呼叫 resolve
            // 如果是 promise 物件,檢視 promise 物件返回的結果
            // 再根據 promise 物件返回的結果,決定呼叫 resolve 還是 reject
            // 宣告一個方法,進行操作
            resolvePromise(newPromise, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)
      } else if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            let x = failCallback(this.reason)
            // 儲存上一個成功回撥的返回值,傳遞給下一個 .then
            // 判斷 x 的值是普通值還是 promise 物件
            // 如果是普通值,直接呼叫 resolve
            // 如果是 promise 物件,檢視 promise 物件返回的結果
            // 再根據 promise 物件返回的結果,決定呼叫 resolve 還是 reject
            // 宣告一個方法,進行操作
            resolvePromise(newPromise, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)
      } else {
        // 因為等待期間你不知道後面執行的是成功的回撥還是失敗的回撥
        // 所以將成功回撥和失敗回撥儲存起來
        this.successCallback.push(() => {
          setTimeout(() => {
            try {
              let x = successCallback(this.value)
              // 儲存上一個成功回撥的返回值,傳遞給下一個 .then
              // 判斷 x 的值是普通值還是 promise 物件
              // 如果是普通值,直接呼叫 resolve
              // 如果是 promise 物件,檢視 promise 物件返回的結果
              // 再根據 promise 物件返回的結果,決定呼叫 resolve 還是 reject
              // 宣告一個方法,進行操作
              resolvePromise(newPromise, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)
        })
        this.failCallback.push(() => {
          setTimeout(() => {
            try {
              let x = failCallback(this.reason)
              // 儲存上一個成功回撥的返回值,傳遞給下一個 .then
              // 判斷 x 的值是普通值還是 promise 物件
              // 如果是普通值,直接呼叫 resolve
              // 如果是 promise 物件,檢視 promise 物件返回的結果
              // 再根據 promise 物件返回的結果,決定呼叫 resolve 還是 reject
              // 宣告一個方法,進行操作
              resolvePromise(newPromise, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)
        })
      }
    })
    return newPromise
  }
}
function resolvePromise(newPromise, x, resolve, reject) {
  if (newPromise === x) {
    return reject(new TypeError('出錯了大爺,不要自己調自己'))
  }
  if (x instanceof MyPromise) {
    // 判斷是否是例項物件就行
    // 如果成功就呼叫 resolve,如果失敗就呼叫 reject
    x.then(resolve, reject)
  } else {
    // 普通值
    resolve(x)
  }
}

四、實現 Promise.all()

  1. 實現 Promise.all()
  2. 實現 Promise.resolve()
  3. 實現 finally()
因為下述程式碼只是新增了幾個靜態方法,剩餘程式碼同第三步一是樣的,所以只展示新增的變化
class MyPromise {
  // all
  static all (array) {
    // 解決非同步併發問題,並且可以按照順序呼叫,並且該方法是靜態方法
    let result = []
    let index = 0
    return new MyPromise((resolve, reject) => {
      function addData (key, value) {
        result[key] = value
        index++
        if (index === array.length) {
          // 是為等所有的非同步操作執行完畢
          resolve(result)
        }
      }
      for (let i = 0; i < array.length; i++) {
        // 迴圈 all 陣列裡面的值
        let current = array[i]
        if (current instanceof MyPromise) {
          // promise 物件
          current.then(value => addData(i, value), reason => reject(reason))
        } else {
          // 普通值
          addData(i, array[i])
        }
      }
    })
  }
  // resolve
  static resolve (value) {
    if (value instanceof MyPromise) return value
    return new MyPromise ((resolve) => resolve(value))
  }
  // finally
  finally (callback) {
    return this.then((value) => {
      return MyPromise.resolve(callback()).then(() => value)
    }, (reason) => {
      return MyPromise.resolve(callback()).then(() => {throw reason})
    })
  }
  catch (failCallback) {
    return this.then(undefined, failCallback)
  }
}

結束 OVER