1. 程式人生 > 實用技巧 >手寫Promise原始碼及方法

手寫Promise原始碼及方法

原文: 本人掘金文章

關注公眾號: 微信搜尋 web全棧進階 ; 收貨更多的乾貨

一、Es5語法

function MyPromise (executor) {
    this.state = 'pending'
    // 成功的值
    this.data = undefined
    // 失敗的原因
    this.reason = undefined
    this.callbacks = []

    // 成功回撥, state 重置為 resolved
    let resolve = (value) => {
      if (this.state !== 'pending') return
      this.state = 'resolved'
      this.data = value
      // 立即執行非同步回撥函式
      setTimeout(_ => {
        this.callbacks.forEach(callback => {
          callback.onResolved(value)
        })
      })
    }

    // 失敗回撥, state 重置為 rejected
    let reject = (reason) => {
      if (this.state !== 'pending') return
      this.state = 'rejected'
      this.reason = reason
      setTimeout(_ => {
        this.callbacks.forEach(callback => {
          callback.onRejected(reason)
        })
      })
    }

    // 如果executor執行報錯,直接執行reject
    try{
      executor(resolve, reject)
    } catch (err) {
      reject(err)
    }
  }

  // Promise原型鏈上的then方法,可以接收兩個引數(且是回撥函式),成功/失敗,並且每次返回的都是一個新的Promise
  MyPromise.prototype.then = function (onResolved, onRejected) {
    let _this = this
    onResolved = typeof onResolved === 'function' ? onResolved : value => value
    onRejected = typeof onRejected === 'function' ? onRejected : reason =>  console.error(reason)
    // promise物件當前狀態為pending
    // 此時並不能確定呼叫onResolved還是onRejected,需要等待promise的狀態改變
    return new MyPromise((resolve, reject) => {

       /*
       * 1、返回的Promise的結果是由onResolved/onrejected決定的
       * 2、返回的是Promise物件 (根據執結果決定Promise的返回結果)
       * 3、返回的不是Promise物件 (該值就是Promise的返回結果)
       * 4、丟擲異常 異常的值為返回的結果
       */

      // 統一處理函式
      function handle (callback) {
        try {
          const result = callback(_this.data)
          if (reject instanceof MyPromise) {
            result.then(value => {
              resolve(value)
            }, reason => {
              reject(reason)
            })
          } else {
            resolve(result)
          }
        } catch (e) {
          reject(e)
        }
      }

      if (_this.state === 'resolved') {
        setTimeout(_ => {
          handle(onResolved)
        })
      }
      if (_this.state === 'rejected') {
        setTimeout(_ => {
          handle(onRejected)
        })
      }
      if (_this.state === 'pending') {
        _this.callbacks.push({
          onResolved() {
            handle(onResolved)
          },
          onRejected() {
            handle(onResolved)
          }
        })
      }
    })
  }

  // 錯誤捕獲
  MyPromise.prototype.catch = function (onRejected) {
    return this.then(null, onRejected)
  }

  MyPromise.resolve = function (value) {
    if (value instanceof MyPromise) return value
    return new MyPromise(resolve => resolve(value))
  }

  MyPromise.reject = function (reason) {
    console.log(reason)
    return new MyPromise((resolve, reject) => reject(reason))  // 返回一個reject狀態Promise物件
  }

  /*
  * Promise.all可以將多個Promise例項包裝成一個新的Promise例項。
  * 同時,成功和失敗的返回值是不同的,成功的時候返回的是一個結果陣列,而失敗的時候則返回最先被reject失敗狀態的值
  */
  MyPromise.all = function (promises) {
    if (typeof promises !== 'object' || promises.constructor !== Array) return console.error('引數需為陣列!')
    let count = 0
    let values = new Array(promises.length)
    return new MyPromise((resolve,reject) => {
      promises.forEach((promise, index) => {
        MyPromise.resolve(promise).then(value => {
          count++
          values[index] = value
          if (count === promises.length) resolve(values)
        }, error => {
          reject(error)
        })
      })
    })
  }

  /*
  * Promise.race 返回陣列例項中最先改變狀態的例項(無論成功還是失敗)
  *
  */
  MyPromise.race = function (promises) {
    return new MyPromise((resolve,reject) => {
      promises.forEach(promise => {
        MyPromise.resolve(promise).then(value => {
          resolve(value)
        }, error => {
          reject(error)
        })
      })
    })
  }

二、Es6 class 語法

class _Promise {
    constructor (executor) {
      const _this = this
      this.state = 'pending'
      // 成功的值
      this.data = undefined
      // 失敗的原因
      this.reason = undefined
      this.callbacks = []

      function resolve (value) {
        if (_this.state === 'pending') return
        _this.state = 'resolved'
        _this.data = value
        setTimeout(_ => {
          _this.callbacks.forEach(callback => {
            callback.onResolved(value)
          })
        })
      }

      function reject (reason) {
        if (_this.state === 'pending') return
        _this.state = 'rejected'
        _this.reason = reason
        setTimeout(_ => {
          _this.callbacks.forEach(callback => {
            callback.onRejected(reason)
          })
        })
      }

      executor(resolve, reject)
    }
    then(onResolved, onRejected) {
      const _this = this
      onResolved = typeof onResolved === 'function' ? onResolved : value => value
      onRejected = typeof onRejected === 'function' ? onRejected : reason => console.error(reason)

      return new _Promise((resolve, reject) => {
        function handle (callback) {
          try {
            const result = callback(_this.data)
            if (reject instanceof _Promise) {
              result.then(value => {
                resolve(value)
              }, reason => {
                reject(reason)
              })
            } else {
              resolve(result)
            }
          } catch (e) {
            reject(e)
          }
        }
        if (_this.state === 'resolved') {
          setTimeout(_ => {
            handle(onResolved)
          })
        }
        if (_this.state === 'rejected') {
          setTimeout(_ => {
            handle(onRejected)
          })
        }
        if (_this.state === 'pending') {
          _this.callbacks.push({
            onResolved() {
              handle(onResolved)
            },
            onRejected() {
              handle(onResolved)
            }
          })
        }
      })
    }
    catch (onRejected) {
      return this.then(null, onRejected);
    }
    static resolve(value) {
      if (value instanceof _Promise) return value;
      return new _Promise(resolve => resolve(value)) // 返回一個resolved狀態的新Promise物件
    }
    static reject(reason) {
      return new _Promise((resolve, reject) => reject(reason)); // 返回一個reject狀態新Promise物件
    }

    static all(promises) {
      let count = 0
      let values = new Array(promises.length)
      return new _Promise((resolve, reject) => {
        promises.forEach((promise, index) => {
          _Promise.resolve(promise).then(value => {
            count++
            values[index] = value
            if (count === promises.length) resolve(values)
          }, reason => reject(reason))
        })
      })
    }

    static race(promises) {
      return new _Promise((resolve, reject) => {
        promises.forEach(promise => {
          _Promise.resolve(promise).then(value => resolve(value))
        }, reason => reject(reason))
      })
    }
  }

三、測試

 /* 測試 */
  window.onload = function () {
    f3()
    let p1 = new MyPromise((resolve,reject) => {
      resolve('p1 執行完畢')
    })
    let p2 = new MyPromise((resolve,reject) => {
      resolve('p2執行完畢')
    })
    let p3 = MyPromise.reject('失敗')

    MyPromise.all([p1, p2]).then(res => {
      console.log(res)
    }).catch(error => {
      console.log(error)
    })

    MyPromise.race([p1, p2, p3]).then(res => {
      console.log(res)
    }).catch(error => {
      console.log(error)
    })
  }

  function f1 () {
    return new _Promise((resolve,reject) => {
      f4()
      resolve(1)
    })
  }

  function f2 () {
    console.log('f2f2f2f2f2f2f2f2')
  }

  function f4 () {
    console.log('f4f4f4f4f4f4f4f4')
  }

  function f3 () {
    let p = f1()
    p.then(res => {
      console.log(res)
    }).catch(err => {
      console.log(err)
    })
    f2()
  }
  // 測試結果
  // f4f4f4f4f4f4f4f4
  // f2f2f2f2f2f2f2f2
  // 失敗
  // ["p1 執行完畢", "p2執行完畢"]
  // p1 執行完畢

參考: https://mp.weixin.qq.com/s?__biz=MzI2NTk2NzUxNg==&mid=2247488162&idx=1&sn=b0c4bdb172cb2076430569632267382d&chksm=ea941051dde39947ec94dcf3bea91250037c8bca83369fe62a61cb013fa79e13c6df5b06c78c&mpshare=1&scene=24&srcid=0822eoIZCeJt7Hy3t2Ygt3Vl&sharer_sharetime=1598055693895&sharer_shareid=50ec90ef8a78d43d2aeefdb38f1cb3a1#rd