天天看點

Promise實作原理(附源碼)

本篇文章主要在于探究 Promise 的實作原理,帶領大家一步一步實作一個 Promise , 不對其用法做說明,如果讀者還對Promise的用法不了解,可以檢視阮一峰老師的ES6 Promise教程。

接下來,帶你一步一步實作一個 Promise

1. Promise 基本結構

new Promise((resolve, reject) => {

  setTimeout(() => {

    resolve('FULFILLED')

  }, 1000)

})           

構造函數Promise必須接受一個函數作為參數,我們稱該函數為handle,handle又包含resolve和reject兩個參數,它們是兩個函數。

定義一個判斷一個變量是否為函數的方法,後面會用到

// 判斷變量否為function
const isFunction = variable => typeof variable === 'function'           

首先,我們定義一個名為 MyPromise 的 Class,它接受一個函數 handle 作為參數

class MyPromise {

  constructor (handle) {

    if (!isFunction(handle)) {

      throw new Error('MyPromise must accept a function as a parameter')

    }

  }

}           

2. Promise 狀态和值

Promise 對象存在以下三種狀态:

  • Pending(進行中)
  • Fulfilled(已成功)
  • Rejected(已失敗)

狀态隻能由 Pending 變為 Fulfilled 或由 Pending 變為 Rejected ,且狀态改變之後不會在發生變化,會一直保持這個狀态。

Promise的值是指狀态改變時傳遞給回調函數的值

上文中handle函數包含 resolve 和 reject 兩個參數,它們是兩個函數,可以用于改變 Promise 的狀态和傳入 Promise 的值

new Promise((resolve, reject) => {

  setTimeout(() => {

    resolve('FULFILLED')

  }, 1000)

})           

這裡 resolve 傳入的 "FULFILLED" 就是 Promise 的值

resolve 和 reject

  • resolve : 将Promise對象的狀态從 Pending(進行中) 變為 Fulfilled(已成功)
  • reject : 将Promise對象的狀态從 Pending(進行中) 變為 Rejected(已失敗)
  • resolve 和 reject 都可以傳入任意類型的值作為實參,表示 Promise 對象成功(Fulfilled)和失敗(Rejected)的值

了解了 Promise 的狀态和值,接下來,我們為 MyPromise 添加狀态屬性和值

首先定義三個常量,用于标記Promise對象的三種狀态

// 定義Promise的三種狀态常量
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'           

再為 MyPromise 添加狀态和值,并添加狀态改變的執行邏輯

class MyPromise {

  constructor (handle) {

    if (!isFunction(handle)) {

      throw new Error('MyPromise must accept a function as a parameter')

    }

    // 添加狀态

    this._status = PENDING

    // 添加狀态

    this._value = undefined

    // 執行handle

    try {

      handle(this._resolve.bind(this), this._reject.bind(this)) 

    } catch (err) {

      this._reject(err)

    }

  }

  // 添加resovle時執行的函數

  _resolve (val) {

    if (this._status !== PENDING) return

    this._status = FULFILLED

    this._value = val

  }

  // 添加reject時執行的函數

  _reject (err) { 

    if (this._status !== PENDING) return

    this._status = REJECTED

    this._value = err

  }

}           

這樣就實作了 Promise 狀态和值的改變。下面說一說 Promise 的核心: then 方法

3. Promise 的 then 方法

Promise 對象的 then 方法接受兩個參數:

promise.then(onFulfilled, onRejected)

參數可選

onFulfilled 和 onRejected 都是可選參數。

  • 如果 onFulfilled 或 onRejected 不是函數,其必須被忽略

onFulfilled 特性

    如果 onFulfilled 是函數:

  • 當 promise 狀态變為成功時必須被調用,其第一個參數為 promise 成功狀态傳入的值( resolve 執行時傳入的值)
  • 在 promise 狀态改變前其不可被調用
  • 其調用次數不可超過一次

onRejected 特性

    如果 onRejected 是函數:

  • 當 promise 狀态變為失敗時必須被調用,其第一個參數為 promise 失敗狀态傳入的值( reject 執行時傳入的值)

多次調用

    then 方法可以被同一個 promise 對象調用多次

  • 當 promise 成功狀态時,所有 onFulfilled 需按照其注冊順序依次回調
  • 當 promise 失敗狀态時,所有 onRejected 需按照其注冊順序依次回調

傳回

then 方法必須傳回一個新的 promise 對象

promise2 = promise1.then(onFulfilled, onRejected);

是以 promise 支援鍊式調用

promise1.then(onFulfilled1, onRejected1).then(onFulfilled2, onRejected2);

這裡涉及到 Promise 的執行規則,包括“值的傳遞”和“錯誤捕獲”機制:

1、如果 onFulfilled 或者 onRejected 傳回一個值 x ,則運作下面的 Promise 解決過程:[[Resolve]](promise2, x)

  • 若 x 不為 Promise ,則使 x 直接作為新傳回的 Promise 對象的值, 即新的onFulfilled 或者 onRejected 函數的參數.
  • 若 x 為 Promise ,這時後一個回調函數,就會等待該 Promise 對象(即 x )的狀态發生變化,才會被調用,并且新的 Promise 狀态和 x 的狀态相同。

下面的例子用于幫助了解:

let promise1 = new Promise((resolve, reject) => {

  setTimeout(() => {

    resolve()

  }, 1000)

})

promise2 = promise1.then(res => {

  // 傳回一個普通值

  return '這裡傳回一個普通值'

})

promise2.then(res => {

  console.log(res) //1秒後列印出:這裡傳回一個普通值

})           
let promise1 = new Promise((resolve, reject) => {

  setTimeout(() => {

    resolve()

  }, 1000)

})

promise2 = promise1.then(res => {

  // 傳回一個Promise對象

  return new Promise((resolve, reject) => {

    setTimeout(() => {

     resolve('這裡傳回一個Promise')

    }, 2000)

  })

})

promise2.then(res => {

  console.log(res) //3秒後列印出:這裡傳回一個Promise

})           

2、如果 onFulfilled 或者onRejected 抛出一個異常 e ,則 promise2 必須變為失敗(Rejected),并傳回失敗的值 e,例如:

let promise1 = new Promise((resolve, reject) => {

  setTimeout(() => {

    resolve('success')

  }, 1000)

})

promise2 = promise1.then(res => {

  throw new Error('這裡抛出一個異常e')

})

promise2.then(res => {

  console.log(res)

}, err => {

  console.log(err) //1秒後列印出:這裡抛出一個異常e

})           

3、如果onFulfilled 不是函數且 promise1 狀态為成功(Fulfilled), promise2 必須變為成功(Fulfilled)并傳回 promise1 成功的值,例如:

let promise1 = new Promise((resolve, reject) => {

  setTimeout(() => {

    resolve('success')

  }, 1000)

})

promise2 = promise1.then('這裡的onFulfilled本來是一個函數,但現在不是')

promise2.then(res => {

  console.log(res) // 1秒後列印出:success

}, err => {

  console.log(err)

})           

4、如果 onRejected 不是函數且 promise1 狀态為失敗(Rejected),promise2必須變為失敗(Rejected) 并傳回 promise1 失敗的值,例如:

let promise1 = new Promise((resolve, reject) => {

  setTimeout(() => {

    reject('fail')

  }, 1000)

})

promise2 = promise1.then(res => res, '這裡的onRejected本來是一個函數,但現在不是')

promise2.then(res => {

  console.log(res)

}, err => {

  console.log(err)  // 1秒後列印出:fail

})           

根據上面的規則,我們來為 完善 MyPromise

修改 constructor : 增加執行隊列

由于 then 方法支援多次調用,我們可以維護兩個數組,将每次 then 方法注冊時的回調函數添加到數組中,等待執行

constructor (handle) {

  if (!isFunction(handle)) {

    throw new Error('MyPromise must accept a function as a parameter')

  }

  // 添加狀态

  this._status = PENDING

  // 添加狀态

  this._value = undefined

  // 添加成功回調函數隊列

  this._fulfilledQueues = []

  // 添加失敗回調函數隊列

  this._rejectedQueues = []

  // 執行handle

  try {

    handle(this._resolve.bind(this), this._reject.bind(this)) 

  } catch (err) {

    this._reject(err)

  }

}           

添加then方法

首先,then 傳回一個新的 Promise 對象,并且需要将回調函數加入到執行隊列中

// 添加then方法

then (onFulfilled, onRejected) {

  const { _value, _status } = this

  switch (_status) {

    // 當狀态為pending時,将then方法回調函數加入執行隊列等待執行

    case PENDING:

      this._fulfilledQueues.push(onFulfilled)

      this._rejectedQueues.push(onRejected)

      break

    // 當狀态已經改變時,立即執行對應的回調函數

    case FULFILLED:

      onFulfilled(_value)

      break

    case REJECTED:

      onRejected(_value)

      break

  }

  // 傳回一個新的Promise對象

  return new MyPromise((onFulfilledNext, onRejectedNext) => {

  })

}           

那傳回的新的 Promise 對象什麼時候改變狀态?改變為哪種狀态呢?

根據上文中 then 方法的規則,我們知道傳回的新的 Promise 對象的狀态依賴于目前 then 方法回調函數執行的情況以及傳回值,例如 then 的參數是否為一個函數、回調函數執行是否出錯、傳回值是否為 Promise 對象。

我們來進一步完善 then 方法:

// 添加then方法

then (onFulfilled, onRejected) {

  const { _value, _status } = this

  // 傳回一個新的Promise對象

  return new MyPromise((onFulfilledNext, onRejectedNext) => {

    // 封裝一個成功時執行的函數

    let fulfilled = value => {

      try {

        if (!isFunction(onFulfilled)) {

          onFulfilledNext(value)

        } else {

          let res =  onFulfilled(value);

          if (res instanceof MyPromise) {

            // 如果目前回調函數傳回MyPromise對象,必須等待其狀态改變後在執行下一個回調

            res.then(onFulfilledNext, onRejectedNext)

          } else {

            //否則會将傳回結果直接作為參數,傳入下一個then的回調函數,并立即執行下一個then的回調函數

            onFulfilledNext(res)

          }

        }

      } catch (err) {

        // 如果函數執行出錯,新的Promise對象的狀态為失敗

        onRejectedNext(err)

      }

    }

    // 封裝一個失敗時執行的函數

    let rejected = error => {

      try {

        if (!isFunction(onRejected)) {

          onRejectedNext(error)

        } else {

            let res = onRejected(error);

            if (res instanceof MyPromise) {

              // 如果目前回調函數傳回MyPromise對象,必須等待其狀态改變後在執行下一個回調

              res.then(onFulfilledNext, onRejectedNext)

            } else {

              //否則會将傳回結果直接作為參數,傳入下一個then的回調函數,并立即執行下一個then的回調函數

              onFulfilledNext(res)

            }

        }

      } catch (err) {

        // 如果函數執行出錯,新的Promise對象的狀态為失敗

        onRejectedNext(err)

      }

    }

    switch (_status) {

      // 當狀态為pending時,将then方法回調函數加入執行隊列等待執行

      case PENDING:

        this._fulfilledQueues.push(fulfilled)

        this._rejectedQueues.push(rejected)

        break

      // 當狀态已經改變時,立即執行對應的回調函數

      case FULFILLED:

        fulfilled(_value)

        break

      case REJECTED:

        rejected(_value)

        break

    }

  })

}           

這一部分可能不太好了解,讀者需要結合上文中 then 方法的規則來細細的分析。

接着修改 _resolve 和 _reject :依次執行隊列中的函數

當 resolve 或  reject 方法執行時,我們依次提取成功或失敗任務隊列當中的函數開始執行,并清空隊列,進而實作 then 方法的多次調用,實作的代碼如下:

// 添加resovle時執行的函數

_resolve (val) {

  if (this._status !== PENDING) return

  // 依次執行成功隊列中的函數,并清空隊列

  const run = () => {

    this._status = FULFILLED

    this._value = val

    let cb;

    while (cb = this._fulfilledQueues.shift()) {

      cb(val)

    }

  }

  // 為了支援同步的Promise,這裡采用異步調用

  setTimeout(() => run(), 0)

}
// 添加reject時執行的函數

_reject (err) { 

  if (this._status !== PENDING) return

  // 依次執行失敗隊列中的函數,并清空隊列

  const run = () => {

    this._status = REJECTED

    this._value = err

    let cb;

    while (cb = this._rejectedQueues.shift()) {

      cb(err)

    }

  }

  // 為了支援同步的Promise,這裡采用異步調用

  setTimeout(run, 0)

}           

這裡還有一種特殊的情況,就是當 resolve 方法傳入的參數為一個 Promise 對象時,則該 Promise 對象狀态決定目前 Promise 對象的狀态。

const p1 = new Promise(function (resolve, reject) {

  // ...

});

const p2 = new Promise(function (resolve, reject) {

  // ...

  resolve(p1);

})           

上面代碼中,p1 和 p2 都是 Promise 的執行個體,但是 p2 的resolve方法将 p1 作為參數,即一個異步操作的結果是傳回另一個異步操作。

注意,這時 p1 的狀态就會傳遞給 p2,也就是說,p1 的狀态決定了 p2 的狀态。如果 p1 的狀态是Pending,那麼 p2 的回調函數就會等待 p1 的狀态改變;如果 p1 的狀态已經是 Fulfilled 或者 Rejected,那麼 p2 的回調函數将會立刻執行。

我們來修改_resolve來支援這樣的特性

  // 添加resovle時執行的函數

  _resolve (val) {

    const run = () => {

      if (this._status !== PENDING) return

      // 依次執行成功隊列中的函數,并清空隊列

      const runFulfilled = (value) => {

        let cb;

        while (cb = this._fulfilledQueues.shift()) {

          cb(value)

        }

      }

      // 依次執行失敗隊列中的函數,并清空隊列

      const runRejected = (error) => {

        let cb;

        while (cb = this._rejectedQueues.shift()) {

          cb(error)

        }

      }

      /* 如果resolve的參數為Promise對象,則必須等待該Promise對象狀态改變後,

        目前Promsie的狀态才會改變,且狀态取決于參數Promsie對象的狀态

      */

      if (val instanceof MyPromise) {

        val.then(value => {

          this._value = value

          this._status = FULFILLED

          runFulfilled(value)

        }, err => {

          this._value = err

          this._status = REJECTED

          runRejected(err)

        })

      } else {

        this._value = val

        this._status = FULFILLED

        runFulfilled(val)

      }

    }

    // 為了支援同步的Promise,這裡采用異步調用

    setTimeout(run, 0)

  }           

這樣一個Promise就基本實作了,現在我們來加一些其它的方法

catch 方法

相當于調用 then 方法, 但隻傳入 Rejected 狀态的回調函數

// 添加catch方法
catch (onRejected) {

  return this.then(undefined, onRejected)

}           

靜态 resolve 方法

// 添加靜态resolve方法
static resolve (value) {

  // 如果參數是MyPromise執行個體,直接傳回這個執行個體

  if (value instanceof MyPromise) return value

  return new MyPromise(resolve => resolve(value))

}           

靜态 reject 方法

// 添加靜态reject方法
           
static reject (value) {

  return new MyPromise((resolve ,reject) => reject(value))

}           

靜态 all 方法

// 添加靜态all方法
static all (list) {

  return new MyPromise((resolve, reject) => {

    /**

     * 傳回值的集合

     */

    let values = []

    let count = 0

    for (let [i, p] of list.entries()) {

      // 數組參數如果不是MyPromise執行個體,先調用MyPromise.resolve

      this.resolve(p).then(res => {

        values[i] = res

        count++

        // 所有狀态都變成fulfilled時傳回的MyPromise狀态就變成fulfilled

        if (count === list.length) resolve(values)

      }, err => {

        // 有一個被rejected時傳回的MyPromise狀态就變成rejected

        reject(err)

      })

    }

  })

}           

靜态 race 方法

// 添加靜态race方法
static race (list) {

  return new MyPromise((resolve, reject) => {

    for (let p of list) {

      // 隻要有一個執行個體率先改變狀态,新的MyPromise的狀态就跟着改變

      this.resolve(p).then(res => {

        resolve(res)

      }, err => {

        reject(err)

      })

    }

  })

}           

finally 方法

finally 方法用于指定不管 Promise 對象最後狀态如何,都會執行的操作

finally (cb) {

  return this.then(

    value  => MyPromise.resolve(cb()).then(() => value),

    reason => MyPromise.resolve(cb()).then(() => { throw reason })

  );

};           

這樣一個完整的 Promsie 就實作了,大家對 Promise 的原理也有了解,可以讓我們在使用Promise的時候更加清晰明了。

完整代碼如下

  // 判斷變量否為function

  const isFunction = variable => typeof variable === 'function'

  // 定義Promise的三種狀态常量

  const PENDING = 'PENDING'

  const FULFILLED = 'FULFILLED'

  const REJECTED = 'REJECTED'


  class MyPromise {

    constructor (handle) {

      if (!isFunction(handle)) {

        throw new Error('MyPromise must accept a function as a parameter')

      }

      // 添加狀态

      this._status = PENDING

      // 添加狀态

      this._value = undefined

      // 添加成功回調函數隊列

      this._fulfilledQueues = []

      // 添加失敗回調函數隊列

      this._rejectedQueues = []

      // 執行handle

      try {

        handle(this._resolve.bind(this), this._reject.bind(this)) 

      } catch (err) {

        this._reject(err)

      }

    }

    // 添加resovle時執行的函數

    _resolve (val) {

      const run = () => {

        if (this._status !== PENDING) return

        // 依次執行成功隊列中的函數,并清空隊列

        const runFulfilled = (value) => {

          let cb;

          while (cb = this._fulfilledQueues.shift()) {

            cb(value)

          }

        }

        // 依次執行失敗隊列中的函數,并清空隊列

        const runRejected = (error) => {

          let cb;

          while (cb = this._rejectedQueues.shift()) {

            cb(error)

          }

        }

        /* 如果resolve的參數為Promise對象,則必須等待該Promise對象狀态改變後,

          目前Promsie的狀态才會改變,且狀态取決于參數Promsie對象的狀态

        */

        if (val instanceof MyPromise) {

          val.then(value => {

            this._value = value

            this._status = FULFILLED

            runFulfilled(value)

          }, err => {

            this._value = err

            this._status = REJECTED

            runRejected(err)

          })

        } else {

          this._value = val

          this._status = FULFILLED

          runFulfilled(val)

        }

      }

      // 為了支援同步的Promise,這裡采用異步調用

      setTimeout(run, 0)

    }

    // 添加reject時執行的函數

    _reject (err) { 

      if (this._status !== PENDING) return

      // 依次執行失敗隊列中的函數,并清空隊列

      const run = () => {

        this._status = REJECTED

        this._value = err

        let cb;

        while (cb = this._rejectedQueues.shift()) {

          cb(err)

        }

      }

      // 為了支援同步的Promise,這裡采用異步調用

      setTimeout(run, 0)

    }

    // 添加then方法

    then (onFulfilled, onRejected) {

      const { _value, _status } = this

      // 傳回一個新的Promise對象

      return new MyPromise((onFulfilledNext, onRejectedNext) => {

        // 封裝一個成功時執行的函數

        let fulfilled = value => {

          try {

            if (!isFunction(onFulfilled)) {

              onFulfilledNext(value)

            } else {

              let res =  onFulfilled(value);

              if (res instanceof MyPromise) {

                // 如果目前回調函數傳回MyPromise對象,必須等待其狀态改變後在執行下一個回調

                res.then(onFulfilledNext, onRejectedNext)

              } else {

                //否則會将傳回結果直接作為參數,傳入下一個then的回調函數,并立即執行下一個then的回調函數

                onFulfilledNext(res)

              }

            }

          } catch (err) {

            // 如果函數執行出錯,新的Promise對象的狀态為失敗

            onRejectedNext(err)

          }

        }

        // 封裝一個失敗時執行的函數

        let rejected = error => {

          try {

            if (!isFunction(onRejected)) {

              onRejectedNext(error)

            } else {

                let res = onRejected(error);

                if (res instanceof MyPromise) {

                  // 如果目前回調函數傳回MyPromise對象,必須等待其狀态改變後在執行下一個回調

                  res.then(onFulfilledNext, onRejectedNext)

                } else {

                  //否則會将傳回結果直接作為參數,傳入下一個then的回調函數,并立即執行下一個then的回調函數

                  onFulfilledNext(res)

                }

            }

          } catch (err) {

            // 如果函數執行出錯,新的Promise對象的狀态為失敗

            onRejectedNext(err)

          }

        }

        switch (_status) {

          // 當狀态為pending時,将then方法回調函數加入執行隊列等待執行

          case PENDING:

            this._fulfilledQueues.push(fulfilled)

            this._rejectedQueues.push(rejected)

            break

          // 當狀态已經改變時,立即執行對應的回調函數

          case FULFILLED:

            fulfilled(_value)

            break

          case REJECTED:

            rejected(_value)

            break

        }

      })

    }

    // 添加catch方法

    catch (onRejected) {

      return this.then(undefined, onRejected)

    }

    // 添加靜态resolve方法

    static resolve (value) {

      // 如果參數是MyPromise執行個體,直接傳回這個執行個體

      if (value instanceof MyPromise) return value

      return new MyPromise(resolve => resolve(value))

    }

    // 添加靜态reject方法

    static reject (value) {

      return new MyPromise((resolve ,reject) => reject(value))

    }

    // 添加靜态all方法

    static all (list) {

      return new MyPromise((resolve, reject) => {

        /**

         * 傳回值的集合

         */

        let values = []

        let count = 0

        for (let [i, p] of list.entries()) {

          // 數組參數如果不是MyPromise執行個體,先調用MyPromise.resolve

          this.resolve(p).then(res => {

            values[i] = res

            count++

            // 所有狀态都變成fulfilled時傳回的MyPromise狀态就變成fulfilled

            if (count === list.length) resolve(values)

          }, err => {

            // 有一個被rejected時傳回的MyPromise狀态就變成rejected

            reject(err)

          })

        }

      })

    }

    // 添加靜态race方法

    static race (list) {

      return new MyPromise((resolve, reject) => {

        for (let p of list) {

          // 隻要有一個執行個體率先改變狀态,新的MyPromise的狀态就跟着改變

          this.resolve(p).then(res => {

            resolve(res)

          }, err => {

            reject(err)

          })

        }

      })

    }

    finally (cb) {

      return this.then(

        value  => MyPromise.resolve(cb()).then(() => value),

        reason => MyPromise.resolve(cb()).then(() => { throw reason })

      );

    }

  }           

如果覺得還行的話,點個贊、收藏一下再走吧。

原文釋出時間為:2018-09-21

本文來自雲栖社群合作夥伴“

前端大學

”,了解相關資訊可以關注“

”。