Contents

Promise 简单实现

Contents

原理:

  • 每个 promise 实例有 value,status,cbs 属性,其中 value 用于存储 resolve 或 reject 时的值,status 保存当前 promise 的状态,初始为 pending,cbs 为一个订阅当前 promise 实例状态的回调数组(初始为空),因为一个 promise 实例可多次调用 then 或 catch
  • 创建一个新的 promise 时,初始化三个属性,以及将 _resolve 和 _reject 传给 new Promise 的参数函数当参数
  • _resolve 和 _reject 的处理非 promise 和非 thenable 以外的值逻辑大体一致,都是 setTimeout 后将当前的 promise 实例状态和值更新,以及查看是否有后续的 promise 订阅,有则依次执行对应回调,然后将回调队列置为空;_reject 函数多出来的逻辑是如果没有订阅则执行 throw value。回调队列为空的情况是,promise 状态变化时,还未有对之进行 then 或 catch 的调用
  • then 的逻辑:无论如何都其返回值都是一个新的 promise 实例。查看当前状态,如果不是 pending 则说明 promise 状态已经改变,然后根据对应的状态返回的新的 promise 实例的构造函数中执行 resolve 或 reject this.value。如果是 pending,则说明需要订阅,则在返回的新 promise 实例的构造函数中生成一个函数,并将之添加到订阅的那个 promise 实例的 cbs 中,这个函数会在被订阅的 promise 实例状态发生变化时触发触发,函数内部会根据被订阅的 promise 实例的状态调用 resolve 或 reject

注意的点:

  • setTimeout 放在哪里合适
  • 如何保证调用 then(fulfill, reject) fulfill 中 throw 的 error 能传到 reject 中
  • 如何保证链式调用过程中每个 promise reject 或 resolve 的 value 得到正常消费和传递
  • 未被 catch 的 error 应该如何处理,throw error 的动作放在哪里合适
  • resolve 如何处理 pending 状态的 promise 和 thenable 的值

核心原理大体如上所言,其余不足的地方应该大多能算作是健壮性的问题,比如处理 xxx 情况。下面是一个极简实现:

const PENDING_STATUS = 'pending'
const FULFILLED_STATUS = 'fulfilled'
const REJECTED_STATUS = 'rejected'
const identity = v => v
const thrower = v => {
  throw v
}
const _nextTick = setTimeout
const isThenable = value =>
  value && Object.prototype.toString.call(value['then']) === '[object Function]'

function _resolve(value) {
  let realValue
  let realStatus = FULFILLED_STATUS

  if (value instanceof PromisE) {
    realValue = value.value

    if (value.status === REJECTED_STATUS) {
      realStatus = REJECTED_STATUS
    } else {
      value.cbs.push((value, status) => {
        if (status === FULFILLED_STATUS) {
          _resolve.call(this, value, FULFILLED_STATUS)
        } else if (status === REJECTED_STATUS) {
          _reject.call(this, value, REJECTED_STATUS)
        }
      })

      return
    }
  } else if (isThenable(value)) {
    const p = new PromisE((resolve, reject) =>
      _nextTick(() => value.then(resolve, reject))
    )

    p.cbs.push((value, status) => {
      if (status === FULFILLED_STATUS) {
        _resolve.call(this, value, FULFILLED_STATUS)
      } else if (status === REJECTED_STATUS) {
        _reject.call(this, value, REJECTED_STATUS)
      }
    })

    return
  } else {
    realValue = value
  }

  _nextTick(() => {
    if (this.status !== PENDING_STATUS) {
      // 已经 resolve 过
      return
    }

    this.value = realValue
    this.status = realStatus

    if (this.cbs.length) {
      this.cbs.forEach(cb => cb(realValue, realStatus))
      this.cbs = []
    }
  })
}

function _reject(value) {
  let realValue

  if (value instanceof PromisE) {
    realValue = value.value
    if (value.status === PENDING_STATUS) {
      value.cbs.push(_reject.bind(this))
      return
    }
  } else {
    realValue = value
  }

  _nextTick(() => {
    if (this.status !== PENDING_STATUS) {
      // 已经 resolve 过
      return
    }

    this.value = realValue
    this.status = REJECTED_STATUS

    if (this.cbs.length) {
      this.cbs.forEach(cb => cb(realValue, REJECTED_STATUS))
      this.cbs = []
    } else {
      throw realValue
    }
  })
}

class PromisE {
  constructor(func = identity) {
    this.status = PENDING_STATUS
    this.value = void 0
    this.cbs = []

    func(_resolve.bind(this), _reject.bind(this))
  }

  then(onFulfilled, onRejected) {
    onFulfilled = onFulfilled instanceof Function ? onFulfilled : identity
    onRejected = onRejected instanceof Function ? onRejected : thrower

    if (this.status === FULFILLED_STATUS) {
      return new PromisE((resolve, reject) => {
        const func = () => {
          try {
            resolve(onFulfilled(this.value))
          } catch (error) {
            try {
              resolve(onRejected(error))
            } catch (error) {
              reject(error)
            }
          }
        }

        func()
      })
    }

    if (this.status === REJECTED_STATUS) {
      return new PromisE((resolve, reject) => {
        const func = () => {
          try {
            resolve(onRejected(this.value))
          } catch (error) {
            reject(error)
          }
        }

        func()
      })
    }

    return new PromisE((resolve, reject) => {
      const func = (value, status) => {
        if (status === FULFILLED_STATUS) {
          try {
            resolve(onFulfilled(value))
          } catch (error) {
            try {
              resolve(onRejected(error))
            } catch (error) {
              reject(error)
            }
          }
        } else if (status === REJECTED_STATUS) {
          try {
            resolve(onRejected(value))
          } catch (error) {
            reject(error)
          }
        }
      }

      this.cbs.push(func)
    })
  }

  catch(onRejected) {
    return this.then(void 0, onRejected)
  }

  static resolve(value) {
    if (value instanceof PromisE) {
      return value
    }

    return new PromisE(resolve => resolve(value))
  }

  static reject(error) {
    return new PromisE((_, reject) => reject(error))
  }
}