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

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

Promise 已是前端不可缺乏的 API,如今已是無處不在。你肯定已經很瞭解 Promise 嗎?若是不是很瞭解,那麼應該瞭解 Promise 的實現原理。若是你以爲你本身挺了解的,那麼你本身實現過 Promise 嗎?jquery

不管如何,瞭解 Promise 的實現方式,對於提高咱們的前端技能都有必定的幫助。git

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

若是你想先直接看效果,能夠看文章最後的完整版,也能夠看 github,github 上包括了單元測試。數組

Promise 的由來

做爲前端,通常最先接觸 Promise 的概念是在 jquery 的 1.5版本發佈的 deferred objects。可是前端最先引入 P romise 的概念的卻不是 jquery,而是dojo,並且 Promise 之因此叫 Promise 也是由於 dojo。Promises/A 標準的撰寫者 KrisZyp 於 2009 年在 google 的 CommonJS 討論組發了一個 貼子,討論了Promise API的設計思路。他聲稱想將這類 API 命名爲 future,可是 dojo 已經實現的 deferred 機制中用到了 Promise這個術語,因此仍是繼續使用 Promise爲此機制命名。

更新日誌

2019-10-12promise

  • 刪除了 this._callbcaQueue,使用 this._nextCallback 替換

    因爲鏈式返回的都是一個新的 Promise,因此回調其實只有一個,無需數組處理瀏覽器

部分術語

  • 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 的執行都是有各自新的生命週期,即各自獨立 Promise 環境。鏈式返回的下一個 Promise 的結果來源於上一個 Promise 的結果。

鏈式原理

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

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

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

可是須要考慮中途斷鏈的狀況,斷鏈後繼續使用鏈式的話,Promise 的狀態多是非 pending 狀態。

這一點剛接觸的時候,是沒那麼容易搞懂的。

默認一開始就使用 new Promise(...).then(...) 進行鏈式調用, then、catch 等的回調函數都是處於 pending 狀態,回調函數會加入異步列隊等待執行。而斷鏈的時候,可能過了幾秒後才從新鏈式調用,那麼 pending 狀態就有可能變爲了 fulfilled 或者 rejected 狀態,須要當即執行,而不須要等待pending 狀態變化後才執行。

不斷鏈的例子以下:

new Promise(...).then(...)

斷鏈的例子以下:

const a = new Promise(...)
// 這中途可能過去了幾秒,pending 狀態就有可能變爲了 fulfilled 或者 rejected 狀態
a.then(...)

因此須要考慮這兩種狀況。

異步列隊

這個須要瞭解宏任務和微任務,可是,不是全部瀏覽器 JavaScript API 都提供微任務這一類的方法。

因此這裏先使用 setTimeout 代替,主要的目的就是要等待 pending 狀態切換爲其餘的狀態(即 executor 的執行),纔會執行後續鏈式的回調操做。

雖然異步 resolve 或者 reject 的時候,使用同步方式也能夠實現。可是原生的 Promise 全部 then 等回調函數都是在異步列隊中執行的,即便是同步 resolve 或者 reject。

new Promise(resolve=>{
  resolve('這是同步 resolve')
})
new Promise(resolve=>{
  setTimeout(()=>{
      resolve('這是異步 resolve')
  })
})

這裏注意一下,後面逐步說明的例子中,前面一些代碼實現是沒考慮異步處理的狀況的,後面涉及到異步 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)
  }

  /**
   * 操做成功
   * @param {Any} value 操做成功傳遞的值
   */
  _onFulfilled = (value) => {
    if (this._status === PENDING) {
      this._status = FULFILLED
      this._nextValue = value
      this._error = undefined
    }
  }

  /**
   * 操做失敗
   * @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 便可。

  • 沒有使用 promiseInstance.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)
      }
    })
  }

  /**
   * 操做成功
   * @param {Any} value 操做成功傳遞的值
   */
  _onFulfilled = (value) => {
    if (this._status === PENDING) {
      this._status = FULFILLED
      this._nextValue = value
      this._error = undefined
      this._throwErrorIfNotCatch()
    }
  }

  /**
   * 操做失敗
   * @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 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)
      }
    })
  }

  /**
   * 操做成功
   * @param {Any} value 操做成功傳遞的值
   */
  _onFulfilled = (value) => {
    if (this._status === PENDING) {
      this._status = FULFILLED
      this._nextValue = value
      this._error = undefined
      this._throwErrorIfNotCatch()
    }
  }

  /**
   * 操做失敗
   * @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._nextCallback

    因爲鏈式返回的都是一個新的 Promise,因此下一步的回調其實只有一個,只有 Promise 的狀態爲 pending 的時候,this._nextCallback 纔會有值(非 pending 的時候回調已經當即執行),同時等待 pending 狀態改變後纔會執行。

    this._nextCallback 都只會在當前 Promise 生命週期中執行一次。

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

    若是非 pending 狀態,那麼當即執行回調函數(若是沒回調函數,跳過)。

    若是是 pending 狀態 ,那麼加入異步列隊(經過 this._nextCallback 傳遞迴調),等待 Promise 狀態爲非 pending 狀態才執行。

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

代碼實現

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 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 的異步回調列隊,會依次執行
      // 改動點:因爲 then、catch 都是返回新的 Promise 因此無需列隊執行,只須要 nextCallback
      // this._callbacQueue = []
      // 異步 resolve 或者 reject 的處理須要等待 pending 狀態轉變後才執行下一步的回調
      this._nextCallback = 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)
      }
    })
  }

  /**
   * 操做成功
   * @param {Any} value 操做成功傳遞的值
   */
  _onFulfilled = value => {
    setTimeout(() => {
      if (this._status === PENDING) {
        this._status = FULFILLED
        this._nextValue = value
        this._error = undefined
        this._nextCallback && this._nextCallback()
        this._throwErrorIfNotCatch()
      }
    })
  }

  /**
   * 操做失敗
   * @param {Any} reason 操做失敗傳遞的值
   */
  _onRejected = reason => {
    setTimeout(() => {
      if (this._status === PENDING) {
        this._status = REJECTED
        this._error = reason
        this._nextValue = undefined
        this._nextCallback && this._nextCallback()
        this._throwErrorIfNotCatch()
      }
    })
  }

  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)
        }
      }

      const nextCallback = () => {
        handle(this._error)
        // error 已經傳遞到下一個 NPromise 了,須要重置,不然會拋出多個相同錯誤
        // 配合 this._throwErrorIfNotCatch 一塊兒使用,
        // 保證執行到最後才拋出錯誤,若是沒有 catch
        this._error = undefined
      }

      if (this._status === PENDING) {
        // 默認不斷鏈的狀況下,then 回電函數 Promise 的狀態爲 pending 狀態(必定的)
        // 如 new NPromise(...).then(...) 是沒斷鏈的
        // 下面是斷鏈的
        // var a = NPromise(...); a.then(...)
        // 固然斷鏈了也多是 pending 狀態
        this._nextCallback = nextCallback
      } else {
        // 斷鏈的狀況下,then 回調函數 Promise 的狀態爲非 pending 狀態的場景下
        // 如 var a = NPromise(...);
        // 過了幾秒後 a.then(...) 繼續鏈式調用就多是非 pending 的狀態了
        nextCallback() // 須要當即執行
      }
    })
  }

  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,上面有單元測試。

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 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 的異步回調列隊,會依次執行
      // 改動點:因爲 then、catch 都是返回新的 Promise 因此無需列隊執行,只須要 nextCallback
      // this._callbacQueue = []
      // 異步 resolve 或者 reject 的處理須要等待 pending 狀態轉變後才執行下一步的回調
      this._nextCallback = 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)
      }
    })
  }

  /**
   * 操做成功
   * @param {Any} value 操做成功傳遞的值
   */
  _onFulfilled = value => {
    setTimeout(() => {
      if (this._status === PENDING) {
        this._status = FULFILLED
        this._nextValue = value
        this._error = undefined
        this._nextCallback && this._nextCallback()
        this._throwErrorIfNotCatch()
      }
    })
  }

  /**
   * 操做失敗
   * @param {Any} reason 操做失敗傳遞的值
   */
  _onRejected = reason => {
    setTimeout(() => {
      if (this._status === PENDING) {
        this._status = REJECTED
        this._error = reason
        this._nextValue = undefined
        this._nextCallback && this._nextCallback()
        this._throwErrorIfNotCatch()
      }
    })
  }

  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)
        }
      }

      const nextCallback = () => {
        handle(this._error)
        // error 已經傳遞到下一個 NPromise 了,須要重置,不然會拋出多個相同錯誤
        // 配合 this._throwErrorIfNotCatch 一塊兒使用,
        // 保證執行到最後才拋出錯誤,若是沒有 catch
        this._error = undefined
      }

      if (this._status === PENDING) {
        // 默認不斷鏈的狀況下,then 回電函數 Promise 的狀態爲 pending 狀態(必定的)
        // 如 new NPromise(...).then(...) 是沒斷鏈的
        // 下面是斷鏈的
        // var a = NPromise(...); a.then(...)
        // 固然斷鏈了也多是 pending 狀態
        this._nextCallback = nextCallback
      } else {
        // 斷鏈的狀況下,then 回調函數 Promise 的狀態爲非 pending 狀態的場景下
        // 如 var a = NPromise(...);
        // 過了幾秒後 a.then(...) 繼續鏈式調用就多是非 pending 的狀態了
        nextCallback() // 須要當即執行
      }
    })
  }

  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)
    })
  })
}

總結

實現 Promise 比較關鍵的點在於狀態的切換,而後鏈式的處理(返回新的 Promise,核心點)。最主要的邏輯仍是 then 方法的處理(核心),理解了 then 方法裏面的邏輯,那麼就瞭解了大部分了。

通過一些測試,除了下面兩點以外:

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

NPromise 單獨用法和效果上基本 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)
})

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

參考文章

相關文章
相關標籤/搜索