如何寫一個讓面試官滿意的 Promise?

Promise 的實現沒那麼簡單,也沒想象中的那麼難,200 行代碼以內就可以實現一個可替代原生的 Promise。

Promise 已經是前端不可缺少的 API,現在已經是無處不在。你確定已經很瞭解 Promise 嗎?如果不是很瞭解,那麼應該瞭解 Promise 的實現原理。如果你覺得你自己挺了解的,那麼你自己實現過 Promise 嗎?

無論如何,瞭解 Promise 的實現方式,對於提升我們的前端技能都有一定的幫助。

下面的代碼都是使用 ES6 語法實現的,不兼容 ES5,在最新的谷歌瀏覽器上運行沒問題。

如果你想先直接看效果,可以看文章最後的完整版,也可以看 github,github 上包括了單元測試。

部分術語

  • executor

    new Promise( function(resolve, reject) {...} /* executor */  );

    executor 是帶有 resolvereject 兩個參數的函數 。

  • onFulfilled

    p.then(onFulfilled, onRejected);

    當 Promise 變成接受狀態(fulfillment)時,該參數作爲 then 回調函數被調用。

  • onRejected
    當Promise變成拒絕狀態(rejection )時,該參數作爲回調函數被調用。

    p.then(onFulfilled, onRejected);
    p.catch(onRejected);

Promise 實現原理

最好先看看 Promises/A+規範,這裏是個人總結的代碼實現的基本原理。

Promise 的三種狀態

  • pending
  • fulfilled
  • rejected

Promise 對象 pending 狀可能會變爲 fulfilled rejected,但是不可以逆轉狀態。

then、catch 的回調方法只有在非 pending 狀態才能執行。

Promise 生命週期

爲了更好理解,本人總結了 Promise 的生命週期,生命週期分爲兩種情況,而且生命週期是不可逆的。

pending -> fulfilld

pending -> rejected

executor、then、catch、finally 的執行都是有各自新的生命週期。

鏈式原理

如何保持 .then.catch.finally 的鏈式調用呢?

其實每個鏈式調用的方法返回一個新的 Promise 實例(其實這也是 Promises/A+ 規範之一)就可以解決這個問題,同時保證了每個鏈式方式的 Promise 的初始狀態爲 pending 狀態,每個 then、catch、finally 都有自身的 Promise 生命週期。

Promise.prototype.then = function(onFulfilled,onReject) {
  return new Promise(() => {
    // 這裏處理後續的邏輯即可
  })
}

但是需要考慮中途斷鏈的情況,斷鏈後繼續使用鏈式的話,Promise 的狀態已經是非 pending 狀態。

不斷鏈的例子如下:

new Promise(...).then(...)
// 這種情況不屬於斷鏈

斷鏈的例子如下:

const a = new Promise(...)
a.then(...)
// 這種情況屬於斷鏈

所以需要考慮這兩種情況。

異步列隊

這個需要了解宏任務和微任務,但是,不是所有瀏覽器 JavaScript API 都提供微任務這一類的方法。

所以這裏先使用 setTimeout 代替

雖然異步 resolve 或者 reject 的時候,不使用異步列隊方式也可以實現,不過原生的 Promise 所有 then 等回調函數都是在異步列隊中執行的。

這裏注意一下,後面逐步說明的例子中,前面的一些是沒使用異步列隊的方式的,後面涉及到異步 resolve 或者 reject 的場景才加上去的。

第一步定義好結構

這裏爲了跟原生的 Promise 做區別,加了個前綴,改爲 NPromise。

定義狀態

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected

定義 Promise 實例方法

class NPromise {
  constructor(executor) {}
  then(onFulfilled, onRejected) {}
  catch(onRejected) {}
  finally(onFinally) {}
}

定義拓展方法

NPromise.resolve = function(value){}
NPromise.reject = function(resaon){}
NPromise.all = function(values){}
NPromise.race = function(values){}

簡單的 Promise

第一個簡單 Promsie 不考慮異步 resolve 的等情況,這一步只是用法像,then 的回調也不是異步的

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

/**
 * @param {Function} executor
 * executor是帶有 resolve 和 reject 兩個參數的函數
 * Promise 構造函數執行時立即調用 executor 函數
 */
class NPromise {
  constructor(executor) {
    // 這裏有些屬性變量是可以不定義的,不過提前定義一下,提高可讀性

    // 初始化狀態爲 pending
    this._status = PENDING
    // 傳遞給 then 的 onFulfilled 參數
    this._nextValue = undefined
    // 錯誤原因
    this._error = undefined
    executor(this._onFulfilled, this._onRejected)
  }

  /**
   * 操作成功
   * 每次運行這個函數的時候需要重置 this._status = PENDING
   * @param {Any} value 操作成功傳遞的值
   */
  _onFulfilled = (value) => {
    if (this._status === PENDING) {
      this._status = FULFILLED
      this._nextValue = value
      this._error = undefined
    }
  }

  /**
   * 操作失敗
   * 每次運行這個函數的時候需要重置 this._status = PENDING
   * @param {Any} reason 操作失敗傳遞的值
   */
  _onRejected = (reason) => {
    if (this._status === PENDING) {
      this._status = REJECTED
      this._error = reason
      this._nextValue = undefined
    }
  }

  then(onFulfilled, onRejected) {
    return new NPromise((resolve, reject) => {
      if (this._status === FULFILLED) {
        if (onFulfilled) {
          const _value = onFulfilled
            ? onFulfilled(this._nextValue)
            : this._nextValue
          // 如果 onFulfilled 有定義則運行 onFulfilled 返回結果
          // 否則跳過,這裏下一個 Promise 都是返回 fulfilled 狀態
          resolve(_value)
        }
      }

      if (this._status === REJECTED) {
        if (onRejected) {
          resolve(onRejected(this._error))
        } else {
          // 沒有直接跳過,下一個 Promise 繼續返回 rejected 狀態
          reject(this._error)
        }
      }
    })
  }

  catch(onRejected) {
    // catch 其實就是 then 的無 fulfilled 處理
    return this.then(null, onRejected)
  }

  finally(onFinally) {
    // 這個後面實現
  }
}

測試例子(setTimeout 只是爲了提供獨立的執行環境)

setTimeout(() => {
  new NPromise((resolve) => {
    console.log('resolved:')
    resolve(1)
  })
    .then((value) => {
      console.log(value)
      return 2
    })
    .then((value) => {
      console.log(value)
    })
})

setTimeout(() => {
  new NPromise((_, reject) => {
    console.log('rejected:')
    reject('err')
  })
    .then((value) => {
      // 這裏不會運行
      console.log(value)
      return 2
    })
    .catch((err) => {
      console.log(err)
    })
})
// 輸出
// resolved:
// 1
// 2
// rejected:
// err

考慮捕獲錯誤

還是先不考慮異步 resolve 的等情況,這一步也只是用法像,then 的回調也不是異步的

相對於上一步的改動點:

  • executor 的運行需要 try catch

    then、catch、finally 都是要經過 executor 執行的,所以只需要 try catch executor 即可。

  • 沒有定義 promise.catch 則直接打印錯誤信息

    不是 throw,而是 console.error,原生的也是這樣的,所以直接 try catch 不到 Promise 的錯誤。

  • then 的回調函數不是函數類型則跳過

    這不屬於錯誤捕獲處理範圍,這裏順帶提一下。

代碼實現

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

function isFunction(fn) {
  return typeof fn === 'function'
}

/**
 * @param {Function} executor
 * executor是帶有 resolve 和 reject 兩個參數的函數
 * Promise 構造函數執行時立即調用 executor 函數
 */
class NPromise {
  constructor(executor) {
    // 這裏有些屬性變量是可以不定義的,不過提前定義一下,提高可讀性

    try {
      // 初始化狀態爲 pending
      this._status = PENDING
      // 傳遞給 then 的 onFulfilled 參數
      this._nextValue = undefined
      // 錯誤原因
      this._error = undefined
      executor(this._onFulfilled, this._onRejected)
    } catch (err) {
      this._onRejected(err)
    }
  }

  /**
   * 如果沒有 .catch 錯誤,則在最後拋出錯誤
   */
  _throwErrorIfNotCatch() {
    setTimeout(() => {
      // setTimeout 是必須的,等待執行完畢,最後檢測 this._error 是否還定義
      if (this._error !== undefined) {
        // 發生錯誤後沒用 catch 那麼需要直接提示
        console.error('Uncaught (in promise)', this._error)
      }
    })
  }

  /**
   * 操作成功
   * 每次運行這個函數的時候需要重置 this._status = PENDING
   * @param {Any} value 操作成功傳遞的值
   */
  _onFulfilled = (value) => {
    if (this._status === PENDING) {
      this._status = FULFILLED
      this._nextValue = value
      this._error = undefined
      this._throwErrorIfNotCatch()
    }
  }

  /**
   * 操作失敗
   * 每次運行這個函數的時候需要重置 this._status = PENDING
   * @param {Any} reason 操作失敗傳遞的值
   */
  _onRejected = (reason) => {
    if (this._status === PENDING) {
      this._status = REJECTED
      this._error = reason
      this._nextValue = undefined
      this._throwErrorIfNotCatch()
    }
  }

  then(onFulfilled, onRejected) {
    return new NPromise((resolve, reject) => {
      const handle = (reason) => {
        function handleResolve(value) {
          const _value = isFunction(onFulfilled) ? onFulfilled(value) : value
          resolve(_value)
        }

        function handleReject(err) {
          if (isFunction(onRejected)) {
            resolve(onRejected(err))
          } else {
            reject(err)
          }
        }

        if (this._status === FULFILLED) {
          return handleResolve(this._nextValue)
        }

        if (this._status === REJECTED) {
          return handleReject(reason)
        }
      }
      handle(this._error)
      // error 已經傳遞到下一個 NPromise 了,需要重置,否則會打印多個相同錯誤
      // 配合 this._throwErrorIfNotCatch 一起使用,
      // 保證執行到最後才拋出錯誤,如果沒有 catch
      this._error = undefined
    })
  }

  catch(onRejected) {
    // catch 其實就是 then 的無 fulfilled 處理
    return this.then(null, onRejected)
  }

  finally(onFinally) {
    // 這個後面實現
  }
}

測試例子(setTimeout 只是爲了提供獨立的執行環境)

setTimeout(() => {
  new NPromise((resolve) => {
    console.log('executor 報錯:')
    const a = 2
    a = 3
    resolve(1)
  }).catch((value) => {
    console.log(value)
    return 2
  })
})

setTimeout(() => {
  new NPromise((resolve) => {
    resolve()
  })
    .then(() => {
      const b = 3
      b = 4
      return 2
    })
    .catch((err) => {
      console.log('then 回調函數報錯:')
      console.log(err)
    })
})

setTimeout(() => {
  new NPromise((resolve) => {
    console.log('直接打印了錯誤信息,紅色的:')
    resolve()
  }).then(() => {
    throw Error('test')
    return 2
  })
})


// executor 報錯:
//  TypeError: Assignment to constant variable.
//     at <anonymous>:97:7
//     at new NPromise (<anonymous>:21:7)
//     at <anonymous>:94:3
//  then 回調函數報錯: TypeError: Assignment to constant variable.
//     at <anonymous>:111:9
//     at <anonymous>:59:17
//     at new Promise (<anonymous>)
//     at NPromise.then (<anonymous>:54:12)
//     at <anonymous>:109:6
// 直接打印了錯誤信息,紅色的:
// Uncaught (in promise) Error: test
//     at <anonymous>:148:11
//     at handleResolve (<anonymous>:76:52)
//     at handle (<anonymous>:89:18)
//     at <anonymous>:96:7
//     at new NPromise (<anonymous>:25:7)
//     at NPromise.then (<anonymous>:73:12)
//     at <anonymous>:147:6

考慮 resolved 的值是 Promise 類型

還是先不考慮異步 resolve 的等情況,這一步也只是用法像,then 的回調也不是異步的

相對於上一步的改動點:

  • 新增 isPromise 判斷方法
  • then 方法中處理 this._nextValue 是 Promise 的情況

代碼實現

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

function isFunction(fn) {
  return typeof fn === 'function'
}

function isPromise(value) {
  return (
    value &&
    isFunction(value.then) &&
    isFunction(value.catch) &&
    isFunction(value.finally)
    // 上面判斷也可以用一句代碼代替,不過原生的試了下,不是用下面的方式
    // value.then instanceof NPromise ||
  )
}

/**
 * @param {Function} executor
 * executor是帶有 resolve 和 reject 兩個參數的函數
 * Promise 構造函數執行時立即調用 executor 函數
 */
class NPromise {
  constructor(executor) {
    // 這裏有些屬性變量是可以不定義的,不過提前定義一下,提高可讀性

    try {
      // 初始化狀態爲 pending
      this._status = PENDING
      // 傳遞給 then 的 onFulfilled 參數
      this._nextValue = undefined
      // 錯誤原因
      this._error = undefined
      executor(this._onFulfilled, this._onRejected)
    } catch (err) {
      this._onRejected(err)
    }
  }

  /**
   * 如果沒有 .catch 錯誤,則在最後拋出錯誤
   */
  _throwErrorIfNotCatch() {
    setTimeout(() => {
      // setTimeout 是必須的,等待執行完畢,最後檢測 this._error 是否還定義
      if (this._error !== undefined) {
        // 發生錯誤後沒用 catch 那麼需要直接提示
        console.error('Uncaught (in promise)', this._error)
      }
    })
  }

  /**
   * 操作成功
   * 每次運行這個函數的時候需要重置 this._status = PENDING
   * @param {Any} value 操作成功傳遞的值
   */
  _onFulfilled = (value) => {
    if (this._status === PENDING) {
      this._status = FULFILLED
      this._nextValue = value
      this._error = undefined
      this._throwErrorIfNotCatch()
    }
  }

  /**
   * 操作失敗
   * 每次運行這個函數的時候需要重置 this._status = PENDING
   * @param {Any} reason 操作失敗傳遞的值
   */
  _onRejected = (reason) => {
    if (this._status === PENDING) {
      this._status = REJECTED
      this._error = reason
      this._nextValue = undefined
      this._throwErrorIfNotCatch()
    }
  }

  then(onFulfilled, onRejected) {
    return new NPromise((resolve, reject) => {
      const handle = (reason) => {
        function handleResolve(value) {
          const _value = isFunction(onFulfilled) ? onFulfilled(value) : value
          resolve(_value)
        }

        function handleReject(err) {
          if (isFunction(onRejected)) {
            resolve(onRejected(err))
          } else {
            reject(err)
          }
        }

        if (this._status === FULFILLED) {
          if (isPromise(this._nextValue)) {
            return this._nextValue.then(handleResolve, handleReject)
          } else {
            return handleResolve(this._nextValue)
          }
        }

        if (this._status === REJECTED) {
          return handleReject(reason)
        }
      }
      handle(this._error)
      // error 已經傳遞到下一個 NPromise 了,需要重置,否則會打印多個相同錯誤
      // 配合 this._throwErrorIfNotCatch 一起使用,
      // 保證執行到最後才拋出錯誤,如果沒有 catch
      this._error = undefined
    })
  }

  catch(onRejected) {
    // catch 其實就是 then 的無 fulfilled 處理
    return this.then(null, onRejected)
  }

  finally(onFinally) {
    // 這個後面實現
  }
}

測試代碼


setTimeout(() => {
  new NPromise((resolve) => {
    resolve(
      new NPromise((_resolve) => {
        _resolve(1)
      })
    )
  }).then((value) => {
    console.log(value)
  })
})

setTimeout(() => {
  new NPromise((resolve) => {
    resolve(
      new NPromise((_, _reject) => {
        _reject('err')
      })
    )
  }).catch((err) => {
    console.log(err)
  })
})

考慮異步的情況

異步 resolve 或者 reject,相對會複雜點,需要放進一個列隊中,然後等待 pending 狀態變爲其他狀態後才執行。

原生 JavaScript 自帶異步列隊,我們可以利用這一點,這裏先使用 setTimeout 代替,所以這個 Promise 的優先級跟 setTimeout 是同一個等級的(原生的是 Promise 優先於 setTimeout 執行)。

相對於上一步的改動點:

  • this._onFulfilled 加上了異步處理
  • this._onRejected 加上了異步處理
  • 新增 this._callbackQueue,初始值爲空數組
  • 新增 this.__runCallbackQueue 方法運行異步列隊

    同步的時候 Promise 的狀態會立即切換爲非 pending 狀態,異步列隊爲空。異步的改變 Promise 狀態的之前,一值是 pending 狀態,那麼 then、catch、finally 的回調函數都會進入列隊中等待執行。

    無論是異步還是同步,this.__runCallbackQueue 都只會在當前 Promise 生命週期中執行一次。

  • then 方法中需要根據 Promise 狀態進行區分處理

    如果非 pending 狀態,那麼立即執行回調函數(如果沒回調函數,跳過)。

    如果是 pending 狀態 ,那麼加入異步列隊,等待 Promise 狀態爲非 pending 狀態才依次執行。

  • then 方法的回調加上了 try catch 捕獲錯誤處理
  • 實現了 finally 方法

代碼實現

// Promises/A+ 規範 https://promisesaplus.com/
// 一個 Promise有以下幾種狀態:
// pending: 初始狀態,既不是成功,也不是失敗狀態。
// fulfilled: 意味着操作成功完成。
// rejected: 意味着操作失敗。

// pending 狀態的 Promise 對象可能會變爲 fulfilled
// 狀態並傳遞一個值給相應的狀態處理方法,也可能變爲失敗狀態(rejected)並傳遞失敗信息。
// 當其中任一種情況出現時,Promise 對象的 then 方法綁定的處理方法(handlers)就會被調用
// then方法包含兩個參數:onfulfilled 和 onrejected,它們都是 Function 類型。
// 當 Promise 狀態爲 fulfilled 時,調用 then 的 onfulfilled 方法,
// 當 Promise 狀態爲 rejected 時,調用 then 的 onrejected 方法,
// 所以在異步操作的完成和綁定處理方法之間不存在競爭。

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

function isFunction(fn) {
  return typeof fn === 'function'
}

function isPromise(value) {
  return (
    value &&
    isFunction(value.then) &&
    isFunction(value.catch) &&
    isFunction(value.finally)
    // 上面判斷也可以用一句代碼代替,不過原生的試了下,不是用下面的方式
    // value.then instanceof NPromise ||
  )
}

/**
 * @param {Function} executor
 * executor是帶有 resolve 和 reject 兩個參數的函數
 * Promise 構造函數執行時立即調用 executor 函數
 */
class NPromise {
  constructor(executor) {
    if (!isFunction(executor)) {
      throw new TypeError('Expected the executor to be a function.')
    }
    try {
      // 初始化狀態爲 PENDING
      this._status = PENDING
      // fullfilled 的值,也是 then 和 catch 的 return 值,都是當前執行的臨時值
      this._nextValue = undefined
      // 當前捕捉的錯誤信息
      this._error = undefined
      // then、catch、finally 的異步回調列隊,會依次執行
      this._callbacQueue = []
      executor(this._onFulfilled, this._onRejected)
    } catch (err) {
      this._onRejected(err)
    }
  }

  /**
   * 如果沒有 .catch 錯誤,則在最後拋出錯誤
   */
  _throwErrorIfNotCatch() {
    setTimeout(() => {
      // setTimeout 是必須的,等待執行完畢,最後檢測 this._error 是否還定義
      if (this._error !== undefined) {
        // 發生錯誤後沒用 catch 那麼需要直接提示
        console.error('Uncaught (in promise)', this._error)
      }
    })
  }

  _runCallbackQueue = () => {
    if (this._callbacQueue.length > 0) {
      // resolve 或者 reject 異步運行的時候,this._callbacQueue 的 length 纔會大於 0
      this._callbacQueue.forEach(fn => {
        fn()
      })
      this._callbacQueue = []
    }
    this._throwErrorIfNotCatch()
  }

  /**
   * 操作成功
   * 每次運行這個函數的時候需要重置 this._status = PENDING
   * @param {Any} value 操作成功傳遞的值
   */
  _onFulfilled = value => {
    setTimeout(() => {
      if (this._status === PENDING) {
        this._status = FULFILLED
        this._nextValue = value
        this._error = undefined
        this._runCallbackQueue()
      }
    })
  }

  /**
   * 操作失敗
   * 每次運行這個函數的時候需要重置 this._status = PENDING
   * @param {Any} reason 操作失敗傳遞的值
   */
  _onRejected = reason => {
    setTimeout(() => {
      if (this._status === PENDING) {
        this._status = REJECTED
        this._error = reason
        this._nextValue = undefined
        this._runCallbackQueue()
      }
    })
  }

  then(onFulfilled, onRejected) {
    return new NPromise((resolve, reject) => {
      const handle = reason => {
        try {
          function handleResolve(value) {
            const _value = isFunction(onFulfilled) ? onFulfilled(value) : value
            resolve(_value)
          }

          function handleReject(err) {
            if (isFunction(onRejected)) {
              resolve(onRejected(err))
            } else {
              reject(err)
            }
          }

          if (this._status === FULFILLED) {
            if (isPromise(this._nextValue)) {
              return this._nextValue.then(handleResolve, handleReject)
            } else {
              return handleResolve(this._nextValue)
            }
          }

          if (this._status === REJECTED) {
            return handleReject(reason)
          }
        } catch (err) {
          reject(err)
        }
      }
      if (this._status === PENDING) {
        // 默認不斷鏈的情況下,then 回電函數 Promise 的狀態爲 pending 狀態
        // 如 new NPromise(...).then(...) 是沒斷鏈的
        // 但是 NPromise.resolve(...).then(...) 是斷鏈的了,相當於
        // var a = NPromise(...); a.then(...)
        this._callbacQueue.push(() => {
          // 先使用 setTimeout 代替
          // 保證狀態切換爲非 PENDING 狀態纔會執行後續的 then、catch 或者 finally 的回調函數
          handle(this._error)
          // error 已經傳遞到下一個 NPromise 了,需要重置,否則會拋出多個相同錯誤
          // 配合 this._throwErrorIfNotCatch 一起使用,
          // 保證執行到最後才拋出錯誤,如果沒有 catch
          this._error = undefined
        })
      } else {
        // 斷鏈的情況下,then 回調函數 Promise 的狀態爲非 pending 狀態
        // 如 var a = NPromise(...); a.then(...) 就是斷鏈的場景
        handle(this._error)
        // error 已經傳遞到下一個 NPromise 了,需要重置,否則會打印多個相同錯誤
        // 配合 this._throwErrorIfNotCatch 一起使用,
        // 保證執行到最後才拋出錯誤,如果沒有 catch
        this._error = undefined
      }
    })
  }

  catch(onRejected) {
    return this.then(null, onRejected)
  }

  finally(onFinally) {
    return this.then(
      () => {
        onFinally()
        return this._nextValue
      },
      () => {
        onFinally()
        // 錯誤需要拋出,下一個 Promise 纔會捕獲到
        throw this._error
      }
    )
  }
}

測試代碼

new NPromise((resolve) => {
  setTimeout(() => {
    resolve(1)
  }, 1000)
})
  .then((value) => {
    console.log(value)
    return new NPromise((resolve) => {
      setTimeout(() => {
        resolve(2)
      }, 1000)
    })
  })
  .then((value) => {
    console.log(value)
  })

拓展方法

拓展方法相對難的應該是 Promise.all,其他的都挺簡單的。

NPromise.resolve = function(value) {
  return new NPromise(resolve => {
    resolve(value)
  })
}

NPromise.reject = function(reason) {
  return new NPromise((_, reject) => {
    reject(reason)
  })
}

NPromise.all = function(values) {
  return new NPromise((resolve, reject) => {
    let ret = {}
    let isError = false
    values.forEach((p, index) => {
      if (isError) {
        return
      }
      NPromise.resolve(p)
        .then(value => {
          ret[index] = value
          const result = Object.values(ret)
          if (values.length === result.length) {
            resolve(result)
          }
        })
        .catch(err => {
          isError = true
          reject(err)
        })
    })
  })
}

NPromise.race = function(values) {
  return new NPromise(function(resolve, reject) {
    values.forEach(function(value) {
      NPromise.resolve(value).then(resolve, reject)
    })
  })
}

最終版

也可以看 github,上面有單元測試。

// Promises/A+ 規範 https://promisesaplus.com/
// 一個 Promise有以下幾種狀態:
// pending: 初始狀態,既不是成功,也不是失敗狀態。
// fulfilled: 意味着操作成功完成。
// rejected: 意味着操作失敗。

// pending 狀態的 Promise 對象可能會變爲 fulfilled
// 狀態並傳遞一個值給相應的狀態處理方法,也可能變爲失敗狀態(rejected)並傳遞失敗信息。
// 當其中任一種情況出現時,Promise 對象的 then 方法綁定的處理方法(handlers)就會被調用
// then方法包含兩個參數:onfulfilled 和 onrejected,它們都是 Function 類型。
// 當 Promise 狀態爲 fulfilled 時,調用 then 的 onfulfilled 方法,
// 當 Promise 狀態爲 rejected 時,調用 then 的 onrejected 方法,
// 所以在異步操作的完成和綁定處理方法之間不存在競爭。

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

function isFunction(fn) {
  return typeof fn === 'function'
}

function isPromise(value) {
  return (
    value &&
    isFunction(value.then) &&
    isFunction(value.catch) &&
    isFunction(value.finally)
    // 上面判斷也可以用一句代碼代替,不過原生的試了下,不是用下面的方式
    // value.then instanceof NPromise ||
  )
}

/**
 * @param {Function} executor
 * executor是帶有 resolve 和 reject 兩個參數的函數
 * Promise 構造函數執行時立即調用 executor 函數
 */
class NPromise {
  constructor(executor) {
    if (!isFunction(executor)) {
      throw new TypeError('Expected the executor to be a function.')
    }
    try {
      // 初始化狀態爲 PENDING
      this._status = PENDING
      // fullfilled 的值,也是 then 和 catch 的 return 值,都是當前執行的臨時值
      this._nextValue = undefined
      // 當前捕捉的錯誤信息
      this._error = undefined
      // then、catch、finally 的異步回調列隊,會依次執行
      this._callbacQueue = []
      executor(this._onFulfilled, this._onRejected)
    } catch (err) {
      this._onRejected(err)
    }
  }

  /**
   * 如果沒有 .catch 錯誤,則在最後拋出錯誤
   */
  _throwErrorIfNotCatch() {
    setTimeout(() => {
      // setTimeout 是必須的,等待執行完畢,最後檢測 this._error 是否還定義
      if (this._error !== undefined) {
        // 發生錯誤後沒用 catch 那麼需要直接提示
        console.error('Uncaught (in promise)', this._error)
      }
    })
  }

  _runCallbackQueue = () => {
    if (this._callbacQueue.length > 0) {
      // resolve 或者 reject 異步運行的時候,this._callbacQueue 的 length 纔會大於 0
      this._callbacQueue.forEach(fn => {
        fn()
      })
      this._callbacQueue = []
    }
    this._throwErrorIfNotCatch()
  }

  /**
   * 操作成功
   * 每次運行這個函數的時候需要重置 this._status = PENDING
   * @param {Any} value 操作成功傳遞的值
   */
  _onFulfilled = value => {
    setTimeout(() => {
      if (this._status === PENDING) {
        this._status = FULFILLED
        this._nextValue = value
        this._error = undefined
        this._runCallbackQueue()
      }
    })
  }

  /**
   * 操作失敗
   * 每次運行這個函數的時候需要重置 this._status = PENDING
   * @param {Any} reason 操作失敗傳遞的值
   */
  _onRejected = reason => {
    setTimeout(() => {
      if (this._status === PENDING) {
        this._status = REJECTED
        this._error = reason
        this._nextValue = undefined
        this._runCallbackQueue()
      }
    })
  }

  then(onFulfilled, onRejected) {
    return new NPromise((resolve, reject) => {
      const handle = reason => {
        try {
          function handleResolve(value) {
            const _value = isFunction(onFulfilled) ? onFulfilled(value) : value
            resolve(_value)
          }

          function handleReject(err) {
            if (isFunction(onRejected)) {
              resolve(onRejected(err))
            } else {
              reject(err)
            }
          }

          if (this._status === FULFILLED) {
            if (isPromise(this._nextValue)) {
              return this._nextValue.then(handleResolve, handleReject)
            } else {
              return handleResolve(this._nextValue)
            }
          }

          if (this._status === REJECTED) {
            return handleReject(reason)
          }
        } catch (err) {
          reject(err)
        }
      }
      if (this._status === PENDING) {
        // 默認不斷鏈的情況下,then 回電函數 Promise 的狀態爲 pending 狀態
        // 如 new NPromise(...).then(...) 是沒斷鏈的
        // 但是 NPromise.resolve(...).then(...) 是斷鏈的了,相當於
        // var a = NPromise(...); a.then(...)
        this._callbacQueue.push(() => {
          // 先使用 setTimeout 代替
          // 保證狀態切換爲非 PENDING 狀態纔會執行後續的 then、catch 或者 finally 的回調函數
          handle(this._error)
          // error 已經傳遞到下一個 NPromise 了,需要重置,否則會拋出多個相同錯誤
          // 配合 this._throwErrorIfNotCatch 一起使用,
          // 保證執行到最後才拋出錯誤,如果沒有 catch
          this._error = undefined
        })
      } else {
        // 斷鏈的情況下,then 回調函數 Promise 的狀態爲非 pending 狀態
        // 如 var a = NPromise(...); a.then(...) 就是斷鏈的場景
        handle(this._error)
        // error 已經傳遞到下一個 NPromise 了,需要重置,否則會打印多個相同錯誤
        // 配合 this._throwErrorIfNotCatch 一起使用,
        // 保證執行到最後才拋出錯誤,如果沒有 catch
        this._error = undefined
      }
    })
  }

  catch(onRejected) {
    return this.then(null, onRejected)
  }

  finally(onFinally) {
    return this.then(
      () => {
        onFinally()
        return this._nextValue
      },
      () => {
        onFinally()
        // 錯誤需要拋出,下一個 Promise 纔會捕獲到
        throw this._error
      }
    )
  }
}

NPromise.resolve = function(value) {
  return new NPromise(resolve => {
    resolve(value)
  })
}

NPromise.reject = function(reason) {
  return new NPromise((_, reject) => {
    reject(reason)
  })
}

NPromise.all = function(values) {
  return new NPromise((resolve, reject) => {
    let ret = {}
    let isError = false
    values.forEach((p, index) => {
      if (isError) {
        return
      }
      NPromise.resolve(p)
        .then(value => {
          ret[index] = value
          const result = Object.values(ret)
          if (values.length === result.length) {
            resolve(result)
          }
        })
        .catch(err => {
          isError = true
          reject(err)
        })
    })
  })
}

NPromise.race = function(values) {
  return new NPromise(function(resolve, reject) {
    values.forEach(function(value) {
      NPromise.resolve(value).then(resolve, reject)
    })
  })
}

總結

經過一些測試,除了下面兩點之外:

  • 使用了 setTimeout 的宏任務列隊外替代微任務
  • 拓展方法 Promise.all 和 Promise.race 只考慮數組,不考慮迭代器。

Promise 單獨用法和效果上基本 100% 跟原生的一致。

如果你不相信,看看 github 上的單元測試,同時你試試下面的代碼:

注意 NPromise 和 Promise 的。

new NPromise((resolve) => {
  resolve(Promise.resolve(2))
}).then((value) => {
  console.log(value)
})

或者

new Promise((resolve) => {
  resolve(NPromise.resolve(2))
}).then((value) => {
  console.log(value)
})

上面的結果都是返回正常的。

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