JavaScript 异步编程原理

Promise

基础版

class Asuna {
  constructor(executor) {
    this._resolveQueue = []
    this._rejectQueue = []
    
		// 箭头函数固定this指向, 否则找不到this._resolveQueue
    const _resolve = (val) => {
      this._resolveQueue.forEach(callback => callback(val))
    }

    const _reject = (val) => {
      this._rejectQueue.forEach(callback => callback(val))
    }
    
		// new Promise()时立即执行executor,并传入resolve和reject
    executor(_resolve, _reject)
  }

  then(resolveFn, rejectFn) {
    this._resolveQueue.push(resolveFn)
    this._rejectQueue.push(rejectFn)
  }
}

Promise 的状态

  1. Promise本质是一个状态机,且状态只能为以下三种:Pending(等待态)Fulfilled(执行态)、`Rejected(拒绝态)

  2. 状态的变更是单向的,只能从Pending -> Fulfilled 或 Pending -> Rejected,状态变更不可逆

image-20200327144533355

// 仅有的三种状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class Asuna {
  constructor(executor) {
    // 初始化为 PENDING
    this._status = PENDING
    this._resolveQueue = []
    this._rejectQueue = []

    const _resolve = (val) => {
      // 状态只能由pending到fulfilled或rejected
      if (this._status !== PENDING) return
      // 变更状态
      this._status = FULFILLED
      // 这里之所以使用一个队列来储存回调,是为了实现规范要求的 "then 方法可以被同一个 promise 调用多次"
      // 如果使用一个变量而非队列来储存回调,那么即使多次p1.then()也只会执行一次回调
      this._resolveQueue.forEach(callback => callback(val))
    }

    const _reject = (val) => {
      if (this._status !== PENDING) return
      this._status = REJECTED
      this._rejectQueue.forEach(callback => callback(val))
    }

    executor(_resolve, _reject)
  }

  then(resolveFn, rejectFn) {
    this._resolveQueue.push(resolveFn)
    this._rejectQueue.push(rejectFn)
  }
}

链式调用

  1. 显然.then()需要返回一个Promise,这样才能找到then方法,所以我们会把then方法的返回值包装成Promise。

  2. .then()的回调需要顺序执行,以上面这段代码为例,虽然中间return了一个Promise,但执行顺序仍要保证是1->2->3。我们要等待当前Promise状态变更后,再执行下一个then收集的回调,这就要求我们对then的返回值分类讨论

then(resolveFn, rejectFn) {
  return new Asuna((resolve, reject) => {
    const fulfilledFn = value => {
      try {
        // 执行当前的Promise的成功回调,并获取返回值
        let x = resolveFn(value)
        // 分类讨论返回值,如果是Promise,那么等待Promise状态变更,否
        if (x instanceof Asuna) {
          x.then(resolve, reject)
        } else {
          resolve(x)
        }
      } catch (err) {
        reject(err)
      }
    }
    this._resolveQueue.push(fulfilledFn)
    const rejectedFn = error => {
      try {
        let x = rejectFn(error)
        if (x instanceof Asuna) {
          x.then(resolve, reject)
        } else {
          resolve(x)
        }
      } catch (error) {
        reject(error)
      }
    }
    this._rejectQueue.push(rejectedFn)
  })
}

值穿透 & 状态已变更情况

我们已经初步完成了链式调用,但是对于 then() 方法,我们还要两个细节需要处理一下

  1. 值穿透:根据规范,如果 then() 接收的参数不是function,那么我们应该忽略它。如果没有忽略,当then()回调不为function时将会抛出异常,导致链式调用中断

  2. 处理状态为resolve/reject的情况:其实我们上边 then() 的写法是对应状态为padding的情况,但是有些时候,resolve/reject 在 then() 之前就被执行(比如Promise.resolve().then()),如果这个时候还把then()回调push进resolve/reject的执行队列里,那么回调将不会被执行,因此对于状态已经变为fulfilledrejected的情况,我们直接执行then回调。

then(resolveFn, rejectFn) {
  // 根据规范,如果then的参数不是function,则我们需要忽略它, 让链式调用继续往下执行
  typeof resolveFn !== 'function' ? resolveFn = value => value : null
  typeof rejectFn !== 'function' ? rejectFn = reason => {
    throw new Error(reason instanceof Error ? reason.message : reason)
  } : null
  return new Asuna((resolve, reject) => {
    const fulfilledFn = value => {
      try {
        let x = resolveFn(value)
        if (x instanceof Asuna) {
          x.then(resolve, reject)
        } else {
          resolve(x)
        }
      } catch (err) {
        reject(err)
      }
    }
    const rejectedFn = error => {
      try {
        let x = rejectFn(error)
        if (x instanceof Asuna) {
          x.then(resolve, reject)
        } else {
          resolve(x)
        }
      } catch (error) {
        reject(error)
      }
    }
    
    // 处理状态为resolve/reject的情况
    if (this.status === PENDING) {
      this._resolveQueue.push(fulfilledFn)
      this._rejectQueue.push(rejectedFn)
    } else if (this.status === FULFILLED) {
      fulfilledFn(this._value)
    } else if (this.status === REJECTED) {
      rejectedFn(this._value)
    }
  })
}