Promise的徹底實現

開發中Promise是及其經常使用的語法,基本上對於異步的處理大都是經過Promise來進行完成。Promise規範有不少,ES6最終採用的是Promise/A+ 規範,因此如下代碼也基本是基於這個規範來進行編寫的。javascript

首先咱們先列舉Promise的全部實例方法跟靜態方法html

實例方法前端

  • then: new Promise((resolve, reject) => {...}).then(() => {console.log('rsolve成功回調')}, () => {console.log('reject失敗回調')})
  • catch: new Promise((resolve, reject) => {...}).catch(() => {console.log('reject失敗方法')})
  • finally: new Promise((resolve, reject) => {...}).finally(() => {console.log('成功失敗都進入')})
  • 以上方法調用都將返回新的Promise

靜態方法java

  • resolve: Promise.resolve(value)返回Promise實例
  • reject: Promise.reject(value)返回Promise實例
  • all: Promise.all(promises): 傳入數組格式的Promise並返回新的Promise實例,成功便按照順序把值返回出來,其中一個失敗則直接變成失敗
  • race: Promise.race(promises): 傳入數組格式的Promise並返回新的Promise實例,成功與失敗取決第一個的完成方式

Promise狀態一旦肯定變不可再發生變化,有如下三個狀態:pendingfulfilledrejected Promise在瀏覽器中的實現是放於微任務隊列中的,須要作微任務的處理(JavaScript中的Event Loop(事件循環)機制node

1.聲明Promise的實例方法

class Promise {
  _value
  _state = 'pending'
  _queue = []
  constructor(fn) {
    if (typeof fn !== 'function') {
      throw 'Promise resolver undefined is not a function'
    }
    /* new Promise((resolve, reject) => { resolve: 成功 reject: 失敗 }) */
    fn(this._resolve.bind(this), this._reject.bind(this))
  }

  // 接收1-2參數,第一個爲成功的回調,第二個爲失敗的回調
  then(onFulfilled, onRejected) {
    // 有可能已經resolve了,由於Promise能夠提早resolve,而後then方法後面註冊
    if (this._state === 'fulfilled') {
      onFulfilled?.(this._value)
      return
    }
    // reject同理
    if (this._state === 'rejected') {
      onRejected?.(this._value)
      return
    }
    // Promise尚未完成,push到一個隊列,到時候完成的時候,執行這個隊列裏面對應的函數
    this._queue.push({
      onFulfilled,
      onRejected,
    })
  }

  // 接收失敗的回調
  catch(onRejected) {
    // 至關於直接調用then傳入失敗的回調
    this.then(null, onRejected)
  }

  // 成功與失敗都執行的回調
  finally(onDone) {
    const fn = () => onDone()
    this.then(fn, fn)
  }
  // 成功resolve
  _resolve(value) {
    // 狀態肯定了,就再也不發生變化了
    if (this._state !== 'pending') return
    this._state = 'fulfilled'

    // 把值存起來,當再次調用的時候直接取這個值就行,由於Promise一旦肯定就不會發生改變了
    this._value = value

    // 執行前面.then方法裏面push函數形式的參數,這樣就執行對應的方法了。
    this._queue.forEach((callback) => {
      callback.onFulfilled?.(this._value)
    })
  }

  // 失敗reject
  _reject(error) {
    // 狀態肯定了,就再也不發生變化了
    if (this._state !== 'pending') return
    this._state = 'rejected'
    this._value = error
    this._queue.forEach((callback) => {
      callback.onRejected?.(this._value)
    })
  }
}
複製代碼

調用邏輯:git

  1. 經過then方法傳入函數形式的參數,也就是onFulfilled => then((onFulfilled, onRejected) => {...})github

  2. then方法中把onFulfilled函數放入_queue這個集合中。 => this._queue.push({ onFulfilled, onRejected })segmentfault

  3. 等異步回調完成,執行resolve函數,這個時候就調用_queue收集好的經過then方法註冊的函數。統一執行這些函數,這樣就達到異步回調完成,執行對應的then方法裏面的函數數組

// 結果打印
const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  }, 1000)
})
p.then((res) => {
  console.log(res) // => success
})

// reject
const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('fail')
  }, 1000)
})
p1.catch((res) => {
  console.log(res) // => fail
})

// finally
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve()
  }, 1000)
})
p2.finally(() => {
  console.log('done') // => done
})
複製代碼

在線代碼演示promise

2. 微任務處理以及返回Promise

a. 進行微任務處理

在瀏覽器中 Promise 完成以後會被推入微任務,因此咱們也須要進行這塊的處理。瀏覽器中使用MutationObserver,node可使用process.nextTick

class Promise {
  ...
  // 推入微任務
  _nextTick(fn) {
    if (typeof MutationObserver !== 'undefined') { // 瀏覽器經過MutationObserver實現微任務的效果
      // 這塊能夠單獨拿出來共用,避免沒必要要的開銷,否則每次都須要生成節點。
      const observer = new MutationObserver(fn)
      let count = 1
      const textNode = document.createTextNode(String(count))
      observer.observe(textNode, {
        characterData: true
      })
      textNode.data = String(++count)
    } else if (typeof process.nextTick !== 'undefined') { // node端經過process.nextTick來實現
      process.nextTick(fn)
    } else {
      setTimeout(fn, 0)
    }
  }
  // 成功resolve
  _resolve(value) {
    // 狀態肯定了,就再也不發生變化了
    if (this._state !== 'pending') return
    // 推入微任務
    this._nextTick(() => {
      this._state = 'fulfilled'
      this._value = value
      this._queue.forEach((callback) => {
        callback.onFulfilled?.(this._value)
      })
    })
  }

  // 失敗reject
  _reject(error) {
    // 狀態肯定了,就再也不發生變化了
    if (this._state !== 'pending') return
    // 推入微任務
    this._nextTick(() => {
      this._state = 'rejected'
      this._value = error
      this._queue.forEach((callback) => {
        callback.onRejected?.(this._value)
      })
    })
  }
  ...
}
複製代碼

效果演示

b. 返回Promise進行鏈式調用

一般Promise會處理多個異步請求,有時候請求之間是有相互依賴關係的。

例如:

const getUser = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({
        userId: '123'
      })
    }, 500)
  })
}

const getDataByUser = (userId) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // ....
      resolve({a: 1})
    }, 500)
  })
}

// 使用
getUser().then((user) => {
  return getDataByUser(user.userId)
}).then((res) => {
  console.log(res)// {a: 1}
})
複製代碼

getDataByUser依賴getUser請求回來的用戶信息,這裏就須要用到Promise鏈式的調用,下面咱們來改動咱們的代碼

class Promise {
  constructor(fn) {
    fn(this._resolve.bind(this), this._reject.bind(this))
  }
  ...
  // 1. 這時候then方法須要返回新的Promise了,由於須要進行鏈式調用,而且下一個then方法接受上一個then方法的值
  // 2. 返回的Promise確定是一個新的Promise,否則就會共用狀態跟返回結果了。
  // 3. 把上一個then方法中的返回值當作下一個Promise resolve的值
  then(onFulfilled, onRejected) {
    // 返回新的Promise
    return new Promise((resolve, reject) => {
      // 有可能已經resolve了,由於Promise能夠提早resolve,而後then方法後面註冊,這個時候能夠直接把值返給函數就行了
      if (this._state === 'fulfilled' && onFulfilled) {
        this._nextTick(onFulfilled.bind(this, this._value))
        return
      }
      if (this._state === 'rejected' && onRejected) {
        this._nextTick(onRejected.bind(this, this._value))
        return
      }
      /* 把當前Promise的then方法的參數跟新的Promise的resolve, reject存到一塊兒,以此來作關聯。 這樣就能把上一個Promise中的onFulfilled與新的Promise中的resolve兩個關聯到一塊,而後即可以作賦值之類的操做了。reject同理 */
      this._queue.push({
        onFulfilled,
        onRejected,
        resolve,
        reject
      })
    })
  }
  // reject同理
  _resolve(value) {
    // 狀態肯定了,就再也不發生變化了
    if (this._state !== 'pending') return

    // 上面示例裏面其實返回的是一個Promise,而不是直接返回的值,因此,這裏咱們須要作一個特殊處理。
    // 就是resolve()的值若是是Promise的對象,咱們須要解析Promise的結果,而後在把值傳給resolve
    if (typeof value === 'object' && typeof value.then === 'function') {
      // 咱們能夠把當前_resolve方法傳遞下去,由於then方法中的參數,一經下個Promise resolve,便會執行then方法對應的參數,而後把對應的值傳入。
      // 這樣就能取到Promise中的值
      // this._resove => obj.onFulfilled?.(this._value)
      // this._reject => obj.onRejected?.(this._value)
      value.then(this._resolve.bind(this), this._reject.bind(this))
      return
    }

    // 推入微任務
    this._nextTick(() => {
      this._state = 'fulfilled'
      this._value = value
      this._queue.forEach((obj) => {
        // 接受onFulfilled返回值
        const val = obj.onFulfilled?.(this._value)
        // reoslve這個值,此時 onFulfilled 是當前Promise then方法中的第一個參數: Promise.then((res) => {consolle.log(res)})
        // obj.resolve是新的Promise的resolve函數,這樣就把then方法中的返回值傳給下一個Promise
        obj.resolve(val)
      })
    })
  }
  ...
}

複製代碼

效果演示

調用邏輯:

  1. 微任務採用MutationObserverprocess.nextTick來進行實現

  2. Promise鏈式調用,這裏經過把then方法中的(onFulfilled, onRejected)參數與新返回的Promise中的(resolve, reject)關聯到一塊兒。

  3. 一旦上一個Promise成功,調用onFulfilled函數,就能夠把onFulfilled中返回的值,放到新的Promise的resolve中。

  4. 若是遇到resolve的值是Promise對象,遞歸進行解析,而後再把值返回出去

完整代碼

class Promise {
  _value
  _state = 'pending'
  _queue = []
  constructor(fn) {
    if (typeof fn !== 'function') {
      throw new Error('Promise resolver undefined is not a function')
    }
    /* new Promise((resolve, reject) => { resolve: 成功 reject: 失敗 }) */
    fn(this._resolve.bind(this), this._reject.bind(this))
  }

  // 接收1-2參數,第一個爲成功的回調,第二個爲失敗的回調
  then(onFulfilled, onRejected) {
    // 返回新的Promise
    return new Promise((resolve, reject) => {
      // 有可能已經resolve了,由於Promise能夠提早resolve,而後then方法後面註冊,這個時候能夠直接把值返給函數就行了
      if (this._state === 'fulfilled' && onFulfilled) {
        this._nextTick(onFulfilled.bind(this, this._value))
        return
      }
      if (this._state === 'rejected' && onRejected) {
        this._nextTick(onRejected.bind(this, this._value))
        return
      }
      // 把當前Promise的then方法的參數跟新的Promise的resolve, reject存到一塊兒,以此來作關聯
      this._queue.push({
        onFulfilled,
        onRejected,
        resolve,
        reject
      })
    })
  }

  // 接收失敗的回調
  catch(onRejected) {
    return this.then(null, onRejected)
  }

  // 成功與失敗都執行的回調
  finally(onDone) {
    return this.then((value) => {
      onDone()
      return value
    }, (value) => {
      // console.log(value)
      onDone()
      throw value
    })
  }

  // 推入微任務
  _nextTick(fn) {
    if (typeof MutationObserver !== 'undefined') { // 瀏覽器
      // 這塊能夠單獨拿出來共用,避免沒必要要的開銷,否則每次都須要生成節點。
      const observer = new MutationObserver(fn)
      let count = 1
      const textNode = document.createTextNode(String(count))
      observer.observe(textNode, {
        characterData: true
      })
      textNode.data = String(++count)
    } else if (typeof process.nextTick !== 'undefined') { // node
      process.nextTick(fn)
    } else {
      setTimeout(fn, 0)
    }
  }
  // 成功resolve
  _resolve(value) {
    // 狀態肯定了,就再也不發生變化了
    if (this._state !== 'pending') return

    // 上面示例裏面其實返回的是一個Promise,而不是直接返回的值,因此,這裏咱們須要作一個特殊處理。
    // 就是若是resolve()的若是是Promise的對象,咱們須要解析Promise的結果,而後在把值傳給resolve
    if (typeof value === 'object' && typeof value.then === 'function') {
      // 咱們能夠把當前_resolve方法傳遞下去,由於then方法中的參數,一經下個Promise resolve,便會執行then方法對應的參數,而後把對應的值傳入。
      // 這樣就能取到Promise中的值
      // this._resove => obj.onFulfilled?.(this._value)
      // this._reject => obj.onRejected?.(this._value)
      value.then(this._resolve.bind(this), this._reject.bind(this))
      return
    }

    // 推入微任務
    this._nextTick(() => {
      this._state = 'fulfilled'
      this._value = value
      this._queue.forEach((obj) => {
        // 使用try catch 來捕獲onFulfilled存在函數內部錯誤的狀況
        try {
          // 接受onFulfilled返回值,若是不存在,把this._value往下傳遞
          const val = obj.onFulfilled ? obj.onFulfilled(this._value) : this._value
          // reoslve這個值,此時 onFulfilled 是當前Promise then方法中的第一個參數: Promise.then((res) => {consolle.log(res)})
          // obj.resolve是新的Promise的resolve函數,這樣就把then方法中的返回值傳給下一個Promise
          obj.resolve(val)
        } catch (e) {
          obj.reject(e)
        }
      })
    })
  }

  // 失敗reject
  _reject(error) {
    if (this._state !== 'pending') return
    this._nextTick(() => {
      this._state = 'rejected'
      this._value = error
      this._queue.forEach((obj) => {
        try {
          const val = obj.onRejected ? obj.onRejected(this._value) : this._value
          // 當前 reject執行完畢以後,會返回新的Promise,應該是能正常resolve的,因此這裏要用 resolve, 不該該繼續使用reject來讓下個Promise執行失敗流程
          obj.resolve(val)
        } catch (e) {
          obj.reject(e)
        }
      })
    })
  }
}
複製代碼

聲明Promise的靜態方法

總共有4個靜態方法: Promise.resolvePromise.rejectPromise.allPromise.race,統一返回的都是新的Promise。

class Promise {
  ...
  /** * 直接resolve */
  static resolve(value) {
    // 是Promise直接返回
    if (value instanceof Promise) {
      return value
    } else if (typeof value === 'object' && typeof value.then === 'function') {
      // 傳入的對象含有then方法
      const then = value.then
      return new Promise((resolve) => {
        then.call(value, resolve)
      })
    } else {
      // 正常返回值,直接返回新的Promise在resolve這個值
      return new Promise((resolve) => resolve(value))
    }
  }

  /** * 直接reject, 測試下Promise.reject並沒作特殊處理,因此直接返回便可。 */
  static reject(value) {
    return new Promise((resolve, reject) => reject(value))
  }

  /** * 傳入數組格式的`Promise`並返回新的`Promise`實例,成功便按照順序把值返回出來,其中一個失敗則直接變成失敗 */
  static all(promises) {
    return new Promise((resolve, reject) => {
      let count = 0
      let arr = []
      // 按照對應的下標push到數組裏面
      promises.forEach((promise, index) => {
        // 轉換成Promise對象
        Promise.resolve(promise).then((res) => {
          count++
          arr[index] = res
          if (count === promises.length) {
            resolve(arr)
          }
        }, err => reject(err))
      })
    })
  }
  
  /** * 傳入數組格式的`Promise`並返回新的`Promise`實例,成功與失敗取決第一個的完成方式 */
  static race(promises) {
    return new Promise((resolve, reject) => {
      promises.forEach((promise, index) => {
        // 轉換成Promise對象
        Promise.resolve(promise).then((res) => {
          // 誰先執行直接resolve, 或reject
          resolve(res)
        }, err => reject(err))
      })
    })
  }
  ...
}
複製代碼

Promise實現完整代碼

class Promise {
  _value
  _state = 'pending'
  _queue = []
  constructor(fn) {
    if (typeof fn !== 'function') {
      throw new Error('Promise resolver undefined is not a function')
    }
    /* new Promise((resolve, reject) => { resolve: 成功 reject: 失敗 }) */
    fn(this._resolve.bind(this), this._reject.bind(this))
  }

  /** * 接收1-2參數,第一個爲成功的回調,第二個爲失敗的回調 * * @param {*} onFulfilled * @param {*} onRejected * @return {*}  * @memberof Promise */
  then(onFulfilled, onRejected) {
    // 返回新的Promise
    return new Promise((resolve, reject) => {
      // 有可能已經resolve了,由於Promise能夠提早resolve,而後then方法後面註冊,這個時候能夠直接把值返給函數就行了
      if (this._state === 'fulfilled' && onFulfilled) {
        this._nextTick(onFulfilled.bind(this, this._value))
        return
      }
      if (this._state === 'rejected' && onRejected) {
        this._nextTick(onRejected.bind(this, this._value))
        return
      }
      // 把當前Promise的then方法的參數跟新的Promise的resolve, reject存到一塊兒,以此來作關聯
      this._queue.push({
        onFulfilled,
        onRejected,
        resolve,
        reject
      })
    })
  }

  /** * 接收失敗的回調 * * @param {*} onRejected * @return {*}  * @memberof Promise */
  catch(onRejected) {
    return this.then(null, onRejected)
  }

  /** * 成功與失敗都執行的回調 * * @param {*} onDone * @return {*}  * @memberof Promise */
  finally(onDone) {
    return this.then((value) => {
      onDone()
      return value
    }, (value) => {
      onDone()
      // 直接報錯,能夠在try catch中捕獲錯誤
      throw value
    })
  }

  /** * 直接resolve * * @static * @param {*} value * @return {*}  * @memberof Promise */
  static resolve(value) {
    if (value instanceof Promise) {
      return value
    } else if (typeof value === 'object' && typeof value.then === 'function') {
      // 傳入的對象含有then方法
      const then = value.then
      return new Promise((resolve) => {
        then.call(value, resolve)
      })
    } else {
      return new Promise((resolve) => resolve(value))
    }
  }

  /** * 直接reject, 測試下reject在Promise.reject中沒作特殊處理 * * @static * @param {*} value * @return {*}  * @memberof Promise */
  static reject(value) {
    return new Promise((resolve, reject) => reject(value))
  }

  /** * 傳入數組格式的`Promise`並返回新的`Promise`實例,成功便按照順序把值返回出來,其中一個失敗則直接變成失敗 * * @static * @param {*} promises * @memberof Promise */
  static all(promises) {
    return new Promise((resolve, reject) => {
      let count = 0
      let arr = []
      if (Array.isArray(promises)) {
        if (promises.length === 0) {
          return resolve(promises)
        }
        promises.forEach((promise, index) => {
          // 轉換成Promise對象
          Promise.resolve(promise).then((res) => {
            count++
            arr[index] = res
            if (count === promises.length) {
              resolve(arr)
            }
          }, err => reject(err))
        })
        return
      } else {
        reject(`${promises} is not Array`)
      }
    })
  }
  
  /** * 傳入數組格式的`Promise`並返回新的`Promise`實例,成功與失敗取決第一個的完成方式 * * @static * @param {*} promises * @return {*}  * @memberof Promise */
  static race(promises) {
    return new Promise((resolve, reject) => {
      if (Array.isArray(promises)) {
        promises.forEach((promise, index) => {
          // 轉換成Promise對象
          Promise.resolve(promise).then((res) => {
            resolve(res)
          }, err => reject(err))
        })
      } else {
        reject(`${promises} is not Array`)
      }
    })
  }

  // 推入微任務
  _nextTick(fn) {
    if (typeof MutationObserver !== 'undefined') { // 瀏覽器
      // 這塊能夠單獨拿出來共用,避免沒必要要的開銷,否則每次都須要生成節點。
      const observer = new MutationObserver(fn)
      let count = 1
      const textNode = document.createTextNode(String(count))
      observer.observe(textNode, {
        characterData: true
      })
      textNode.data = String(++count)
    } else if (typeof process.nextTick !== 'undefined') { // node
      process.nextTick(fn)
    } else {
      setTimeout(fn, 0)
    }
  }
  // 成功resolve
  _resolve(value) {
    // 狀態肯定了,就再也不發生變化了
    if (this._state !== 'pending') return

    // 上面示例裏面其實返回的是一個Promise,而不是直接返回的值,因此,這裏咱們須要作一個特殊處理。
    // 就是若是resolve()的若是是Promise的對象,咱們須要解析Promise的結果,而後在把值傳給resolve
    if (typeof value === 'object' && typeof value.then === 'function') {
      // 咱們能夠把當前_resolve方法傳遞下去,由於then方法中的參數,一經下個Promise resolve,便會執行then方法對應的參數,而後把對應的值傳入。
      // 這樣就能取到Promise中的值
      // this._resove => obj.onFulfilled?.(this._value)
      // this._reject => obj.onRejected?.(this._value)
      value.then(this._resolve.bind(this), this._reject.bind(this))
      return
    }

    // 經過打印測試,若是直接在線程裏進行resolve, 狀態跟值好像是直接就改變了,並無執行完主流程,在執行微任務的時候進行修改的。
    // 因此把狀態改變和值的修改移出了微任務,只有在走回調的時候才經過微任務進行處理
    this._state = 'fulfilled'
    this._value = value

    // 推入微任務
    this._nextTick(() => {
      this._queue.forEach((obj) => {
        // 使用try catch 來捕獲onFulfilled存在函數內部錯誤的狀況
        try {
          // 接受onFulfilled返回值,若是不存在,把this._value往下傳遞
          const val = obj.onFulfilled ? obj.onFulfilled(this._value) : this._value
          // reoslve這個值,此時 onFulfilled 是當前Promise then方法中的第一個參數: Promise.then((res) => {consolle.log(res)})
          // obj.resolve是新的Promise的resolve函數,這樣就把then方法中的返回值傳給下一個Promise
          obj.resolve(val)
        } catch (e) {
          obj.reject(e)
        }
      })
    })
  }

  // 失敗reject
  _reject(error) {
    if (this._state !== 'pending') return
    this._state = 'rejected'
    this._value = error

    this._nextTick(() => {
      this._queue.forEach((obj) => {
        try {
          // 用戶傳入的函數內部錯誤捕獲
          if (obj.onRejected) {
            const val = obj.onRejected(this._value)
            // 當前 reject執行完畢以後,會返回新的Promise,應該是能正常resolve的,因此這裏要用 resolve, 不該該繼續使用reject來讓下個Promise執行失敗流程
            obj.resolve(val)
          } else {
            // 遞歸傳遞reject錯誤
            obj.reject(this._value)
          }
        } catch (e) {
          obj.reject(e)
        }
      })
    })
  }
}
複製代碼

完整演示效果

博客原文地址

本項目完整代碼:GitHub

以上就是Promise的實現方案,固然這個跟完整的Promises/A+規範是有區別的。這裏只是用作於學習之用。

QQ羣:前端打雜羣

公衆號:冬瓜書屋

相關文章
相關標籤/搜索