简单实现Promise

1,071 阅读2分钟

then、catch、finally设计思路

then、catch、finally的回调函数必须要在ajax、异步函数获取到数据或者异常后再执行,所有then、catch、finally的回调函数就必须要先储存起来,由此可推断使用发布订阅最为合适,执行then、catch、finally时把回调函数推到订阅事件数组内,等到ajax、异步函数响应后回传数据或者异常遍历整个实例同时执行所有的回调

链式调用设计思路

then、catch、finally能使用调用一般的做法是return this,但如果这样实现连续调用then就会造成Promise里的data受到污染,所有调用then、catch、finally后应该return一个新的Promise实例而不是this

Promise数据结构设计思路

MyPromise的实例是一颗树,每一个节点本身有可能订阅then、catch、finally的回调,节点有tasks来储存通过then生成的MyPromise实例出来的并且订阅不同事件的新节点

代码

let id = 1
class MyPromise {
  static resolve (data) {
    return data ? new MyPromise((resolve, reject) => {
      resolve(data)
    }) : new MyPromise()
  }

  static reject (err) {
    return err ? new MyPromise((resolve, reject) => {
      reject(err)
    }) : new MyPromise()
  }

  static wait ({ callback, errCallback }) {
    const p = new MyPromise()
    p.callback = callback
    p.errCallback = errCallback
    return p
  }

  static all (promises) {
    return new MyPromise((resolve, reject) => {
      const len = promises.length
      const allRes = Array.from({ length: len }, () => ({ data: null, ready: false }))
      for (let i = 0; i < len; i++) {
        const promise = promises[i]
        promise.then(res => {
          allRes[i] = {
            data: res,
            ready: true
          }
          if (allRes.every(item => item.ready)) {
            resolve(allRes.map(item => item.data))
          }
        }).catch(err => {
          reject(err)
        })
      }
    })
  }

  constructor(action) {

    this.id = id++  // 唯一id
    
    this.tasks = [] // 订阅任务

    this._data  // 成功放回的data

    this._err  // 失败信息

    this.status = 'wait'

    try {
      if (action){
        action(this._resolve.bind(this), this._reject.bind(this))
      }
    } catch (err) {
      this._reject(err)
    }
  }

  then (callback, errCallback) {
    const ploys = {
      wait: () => {
        return MyPromise.wait({ callback, errCallback })
      },
      resolve: () => {
        const res =  callback ? callback(this._data) : null
        return MyPromise.resolve(res)
      },
      reject: () => {
        return MyPromise.reject(this._err)
      }
    }
    const p = ploys[this.status]()
    this.tasks.push(p)
    return p
  }

  catch (errCallback) {
    if (this.status == 'reject') {
      errCallback(this._err)
    }
    const p = this.then(null, errCallback)
    return p
  }

  finally (finallyCallback) {
    const p = this.then()
    p.finallyCallback = finallyCallback
    return p
  }

  _resolve (data) {
    setTimeout(() => {
      this.status = 'resolve'
      this._data = data
      this._runTasks(this, data)
    })
  }

  _reject (err) {
    setTimeout(() => {
      this.status = 'reject'
      this._err = err
      this._runCatchTasks(this, err)
    })
  }

  _runTasks(promise, data) {
    promise.tasks.forEach(p => {
      let res = data
      if (p.callback) {
        res = p.callback(data)
      }
      if (p.finallyCallback) {
        p.finallyCallback()
      }
      p._runTasks(p, res)
    })
  }

  _runCatchTasks(promise, err) {
    promise.tasks.forEach(p => {
      if (p.errCallback) {
        p.errCallback(err)
      }
      if (p.finallyCallback) {
        p.finallyCallback()
      }
      p._runCatchTasks(p, err)
    })
  }
}

export default MyPromise