es6 实现一个promise

293 阅读3分钟

前言:promise的js实现网上有很多,但基本都是基于es5的,es6有着更简介的语法,为什么不尝试一下呢?

阶段一:

只支持链式调用不支持其他api

const pending = 'pending'
const resolved = 'resolved'
const rejected = 'rejected'
class MyPromise {
  constructor (cb) {
    this._status = pending // 初始化状态
    this._data = null // 成功传递的消息
    this._error = null // 失败传递的消息
    this.successSubs = [] // resolve后需要执行的回调队列
    this.failSubs = [] // reject后需要执行的回调队列
    if (typeof cb === 'function') {
      cb(this.resolve.bind(this), this.reject.bind(this)) // 绑定当前实例
    } else if(cb && typeof cb !== 'function') {
      return new TypeError('MyPromise constructor must be a function')
    }
  }
  resolve (_data) {
    // 这里应该有两种情况
    // 1 调用then方法后订阅了
    // 2 直接调用Promise.resolve()方法
    // 情况1:
    if (this._status === pending) {
      this._status = resolved
      this._data = _data
      this.successSubs.forEach(fn => fn())
    }
  }
  reject (_error) {
    if (this._status === pending) {
      this._status = rejected
      this._error = _error
      this.failSubs.forEach(fn => fn())
    }
  }
  resolvePromise (x,resolve, reject) {
    // 如果返回的是MyPromise实例
    if (x instanceof MyPromise) {
      x.then(data => {
        resolve(data)
        },
        error => {
          reject(error)
        })
    } else {
      // 普通值:直接执行then返回的新promise方法的resolve,后一个then属于这个实例的,订阅队列也是属于这个实例的
      resolve(x)
    }
  }
  then (success, fail) {
    // 链式调用 后一个then调用前一个then返回的promise实例
    let p = new MyPromise((resolve, reject) => {
      if (this._status === pending) {
        // promise实例还在pending状态调用了then方法,增加订阅者
        if (success && typeof success === 'function') {
          this.successSubs.push(() => {
            try {
              // 用户调用then方法,callback函数,两种情况
              // 1 非promise对象: return 什么就作为参数传递给下个then什么
              // 2 promise对象:择要等用户的promise的有结果才执行下一个函数
              let x = success(this._data)
              // 统一封装到一个函数中处理
              this.resolvePromise(x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          })
        }
        if (fail && typeof fail === 'function') {
          this.failSubs.push(() => {
            try {
              let x  = fail(this._error)
              this.resolvePromise(x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          })
        }
      } else if (this._status === resolved) {
        try {
          let x  = success(this._data)
          this.resolvePromise(x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      } else if(this._status === rejected) {
        try {
          let x  = fail(this._error)
          this.resolvePromise(x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      }
    })
    return p
  }
}

阶段二:

实现其他api:

Promise.resolve()和Promise.reject()

这两个方法都返回一个非pending状态的promise对象,同时Promise类才有的方法,实例没有该方法。和es6中class static方法完美契合。

...
 // 直接调用了Promise.resolve方法
  static resolve (value) {
    return new MyPromise(resolve => {
      resolve(value)
    })
  }
  // 直接调用了Promise.reject
  static reject(reason) {
    return new MyPromise((undefined, reject) => {
      reject(reason)
    })
  }
...

Promise.all

Promise.all(iterable): 方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);如果参数中  promise 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 promise 的结果。

整理思路: 1 判断一个值是否可迭代,可以用Symbol.iterator属性判断,直接用for...of迭代 2 如果全部正常(没有reject)则可以将该实例resolve。 3 调用resolve的时机,因为是异步的,不晓得所有的iterable啥时候执行完,所以每次push的时候判断一下是否可以结束,可以结束就resolve。 4 reject则只需要rejecte当前失败的reason,前面的全部丢弃 5 static私有方法符合需求

static all (iterable) {
    if (iterable[Symbol.iterator]) {
      return new MyPromise((resolve, reject) => {
        let resolveArr = []
        let len = iterable.length
        // 检查是不是所有的promise都完成了
        function checkAll () {
          if (resolveArr.length === len) {
            resolve(resolveArr)
          }
        }
        try {
          for (let x of iterable) {
            // 每项可以是Promise值和其他值
            if (x instanceof MyPromise) {
              x.then(data => {
                resolveArr.push(data)
                checkAll()
              }, reason => {
                reject(reason)
              })
            } else {
              resolveArr.push(x)
              checkAll()
            }
          }
        } catch (e) {
          reject(e)
        }
      })
    } else {
      // 不是可迭代对象:抛出一个错误
      let str = ({}).toString.call(iterable)
      let reg = /^\[object\s([A-Z][a-z]{2,8})\]$/
      let matchArr = str.match(reg)
      let msg = (matchArr && matchArr[1]) || str
      throw new TypeError( msg + ': is not iterable')
    }
  }

测试mdn的例子: 结果返回:

MyPromise
与mdn的一致
原生Promise
再看一个例子:
image.png
原生的promise返回结果顺序和传入的一致,我们实现的是谁先resolve就谁先push,显然还需要保证顺序。 脑海里冒出如下方案:

1 for循环或者forEach提供了参数index, 和值value方便我们直接赋值,但是Set和Map不支持,而且我们结束的判断resolveArr.length === iterable.length有可能会碰到坑。

2 提供一个key,用于标记顺序 目前就想到两个方案,只能选择方案2:

static all (iterable) {
    if (iterable[Symbol.iterator]) {
      return new MyPromise((resolve, reject) => {
        let resolveArr = []
        let len = iterable.length
        // 检查是不是所有的promise都完成了
        function checkAll () {
          if (resolveArr.length === len) {
            resolve(resolveArr.sort((a, b) => {
              return a.key - b.key
            }).map(v => v.data))
          }
        }
        try {
          let key = -1
          for (let x of iterable) {
            key++
            // 每项可以是Promise值和其他值
            if (x instanceof MyPromise) {
              x.then(data => {
                resolveArr.push({data, key})
                checkAll()
              }, reason => {
                reject(reason)
              })
            } else {
              resolveArr.push({data:x, key})
              checkAll()
            }
          }
        } catch (e) {
          reject(e)
        }
      })
    } else {
      // 不是可迭代对象:抛出一个错误
      let str = ({}).toString.call(iterable)
      let reg = /^\[object\s([A-Z][a-z]{2,8})\]$/
      let matchArr = str.match(reg)
      let msg = (matchArr && matchArr[1]) || str
      throw new TypeError( msg + ': is not iterable')
    }
  }

检测一下:

image.png

Promise.race

Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。

race方法与all方法类似,但是相对来说更简单:

static race (iterable) {
    if (iterable[Symbol.iterator]) {
      return new MyPromise((resolve, reject) => {
        try {
          for (let x of iterable) {
            // 每项可以是Promise值和其他值
            if (x instanceof MyPromise) {
              x.then(data => {
                resolve(data)
              }, reason => {
                reject(reason)
              })
            } else {
              resolve(x)
            }
          }
        } catch (e) {
          reject(e)
        }
      })
    } else {
      // 不是可迭代对象:抛出一个错误
      let str = ({}).toString.call(iterable)
      let reg = /^\[object\s([A-Z][a-z]{2,8})\]$/
      let matchArr = str.match(reg)
      let msg = (matchArr && matchArr[1]) || str
      throw new TypeError( msg + ': is not iterable')
    }
  }

测试mdn例子

image.png