参考 Promise/A+ 规范和测试用例手写 Promise

1,210 阅读19分钟

前言

  这可能是手写promise较清晰的文章之一。

  由浅至深逐步分析了原生测试用例,以及相关Promise/A+规范。阅读上推荐以疑问章节为切入重点,对比Promise/A+规范与ECMAScript规范的内在区别与联系,确定怎样构建异步任务和创建promise实例。然后开始手写章节,过程中代码与测试可参考 promise-coding 仓库。

  也试着回答以下关键问题。

  • 什么是广义对象?
  • 如何检验promise类型?
  • promisethenable两类型有何区别?

疑问

  如果不太清楚Promise,建议参考《ECMAScript 6 入门》,预习下Promise相关用法知识。

  除此之外,对规范也要有大致认识,我们将根据几个疑问来细致阐述。

什么是 Promise/A+ 规范?

  Promise有多种社区规范,例如 Promise/APromise/BPromise/DPromises/KISS 等。

  Promise/A+Promise/A之上,规范了术语并拓展了参数行为,省略了一些有问题的部分。

  Promise/A+有很多实现,例如第三方库 qwhenbluebird 等。实际上任何Promise通过测试,都被认为是对Promise/A+规范的一种实现。

Promise/A+规范官方测试用例为 promises-aplus-tests

原生 Promise 实现了 Promise/A+?

  在Promise/A+规范 The ECMAScript Specification 章节中。

The ECMAScript Specification
...
Largely due to the actions of the Promises/A+ community, the Promise global specified by ECMAScript and present in any conforming JavaScript engine is indeed a Promises/A+ implementation!

  叙述了JavaScript引擎中的Promise也是对Promise/A+规范的一种实现。

  为什么呢?

  Promise/A+规范内容上仅说明了Promise状态和then方法。

  ECMAScript规范不仅规定Promise为构造函数,还添加了静态方法,例如Promise.resolvePromise.allPromise.race等,新增了原型方法Promise.prototype.catchPromise.prototype.finally等。其中Promise.prototype.then相关内容,则是根据Promise/A+规范转化而来。

  我们知道,JavaScript就是对ECMAScript规范的一种实现,而ECMAScript规范中Promise.prototype.then相关内容又继承了Promise/A+规范。

  那么可以说,JavaScript引擎中的Promise,即原生Promise,就是对Promise/A+规范的一种实现。

如何构建异步任务?

  Promise/A+规范规定then方法接收两个参数。

promise.then(onFulfilled, onRejected)

  在 2.2.4 小结中明确了参数必须以异步形式运行。

2.2.4. onFulfilled or onRejected must not be called until the execution context stack contains only platform code.

  注解 3.1 补充可用宏任务setTimeoutsetImmediate,或者微任务MutationObserver(浏览器环境)和process.nextTicknode环境)达到异步。

3.1. ...In practice, this requirement ensures that onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack. This can be implemented with either a "macro-task" mechanism such as setTimeout or setImmediate, or with a "micro-task" mechanism such as MutationObserver or process.nextTick.

  综上所述,Promise/A+规范仅规定了参数以异步形式运行,并未规定是宏任务还是微任务。

注意V8引擎内部为微任务,考虑一致性推荐 queueMicrotask 创建微任务,兼容性相对较好

如何创建 promise?

  Promise/A+规范并未提及如何创建promise

  ECMAScript6规范发布以来,多数是以构造函数方式创建promise

new Promise(executor)

  实际上在此之前,还流行一种Deferred方式,例如 JQuery.Deferred

$.Deferred()

  我们以定时器为例,对比下两者差异。

// ECMAScript6 Promise
const promise = new Promise(resolve => {
  setTimeout(() => {
    resolve(1)
  }, 1000)
})

promise.then(v => {
  console.log(v) // 1
})

// JQuery Deferred
const deferred = $.Deferred()

deferred.promise().then(v => {
  console.log(v) // 1
})

setTimeout(() => {
  deferred.resolve(1)
}, 1000)

  你也注意到了吧,Deferred方式相对更加灵活,可以在任何时机修改状态。而Promise方式自由度减少了很多,不仅代码层级多了一层,而且只能在函数参数中修改状态。

  可能你会问,那为什么TC39放弃了Deferred,而决定了Promise构造器方式呢?

  Deferred方式存在一个较严重的缺陷,即在业务流程阶段,不容易捕获异常。

const deferred = $.Deferred()

deferred.promise().catch(v => {
  console.log(v)
})

;(function () {
  throw new Error() // Uncaught Error

  deferred.resolve(1)
})()

  如果想让promise捕获异常,业务代码可修改为。

;(function () {
  try {
    throw new Error()
  } catch (error) {
    deferred.reject(error)
  }

  deferred.resolve(1)
})()

  而Promise构造器方式则非常容易。

const promise = new Promise(resolve => {
  throw new Error()

  resolve(1)
})

promise.catch(v => {
  console.log(v) // Error
})

  两相比较下ECMAScript6确定了以构造器方式创建promise

个人认为Deferred更多是一个发布订阅器,而Promise则相对更加强大,是一个异步流程解决方案,ECMAScript6规范将其独立为一个模块是相当合理的

手写

  Promise/A+更多地是规范了算法逻辑,并未规定语法层面的实现方式。

  我们可以参考原生Promise语法,并结合简单用例,手写以符合Promise/A+规范。

注意Promise/A+规范相关内容将特别标注

实例初始属性

  原生创建Promise实例。

new Promise(() => {})
// {
//   [[PromiseState]]: 'pending',
//   [[PromiseResult]]: undefined,
// }

  相关特征包括。

  • Promise为构造函数
  • 默认状态为pending,默认结果为undefined
  • 三种状态分别为等待态pending、解决态fulfilled和拒绝态rejected——「Promise/A+ 2.1」

  代码编写如下,其中属性[[PromiseState]]用于保存状态,[[PromiseResult]]用于保存结果。

const PromiseState = '[[PromiseState]]'
const PromiseResult = '[[PromiseResult]]'

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class Promise {
  [PromiseState] = PENDING;
  [PromiseResult] = undefined
}

ES2020规范 proposal-class-fields 允许实例属性定义在类内部的最顶层,相对更加清晰简洁

executor 执行器

  原生Promise传参函数。

new Promise(function executor() {
  console.log(1) // 1
})
console.log(2) // 2

new Promise((resolve, reject) => {
  resolve(3)
})
// {
//   [[PromiseState]]: 'fulfilled',
//   [[PromiseResult]]: 3,
// }

new Promise((resolve, reject) => {
  reject(4)
})
// {
//   [[PromiseState]]: 'rejected',
//   [[PromiseResult]]: 4,
// }

  相关特征包括。

  • 实例创建过程中参数executor将同步执行
  • 执行器executor包括resolvereject两个函数参数,resolve执行实例状态修改为解决态,reject执行实例状态修改为拒绝态

  以下为优化代码,注意私有方法用箭头函数,可将内部this指向实例对象。

class Promise {
  ...

  #resolve = value => {
    this[PromiseState] = FULFILLED
    this[PromiseResult] = value
  }
  #reject = reason => {
    this[PromiseState] = REJECTED
    this[PromiseResult] = reason
  }

  constructor(executor) {
    executor(this.#resolve, this.#reject)
  }
}

ES2020规范 proposal-class-fields 允许实例定义私有属性或方法,仅可在类内部使用,外部无法使用

状态不可变性

  原生Promise修改状态。

new Promise((resolve, reject) => {
  resolve(1)
  resolve(2)
})
// {
//   [[PromiseState]]: 'fulfilled',
//   [[PromiseResult]]: 1,
// }

new Promise((resolve, reject) => {
  resolve(3)
  reject(4)
})
// {
//   [[PromiseState]]: 'fulfilled',
//   [[PromiseResult]]: 3,
// }

  相关特征包括。

  代码优化为。

#resolve = value => {
  if (this[PromiseState] === PENDING) {
    this[PromiseState] = FULFILLED
    this[PromiseResult] = value
  }
}
#reject = reason => {
  if (this[PromiseState] === PENDING) {
    this[PromiseState] = REJECTED
    this[PromiseResult] = reason
  }
}

方法传参

  原生Promisethen方法传参。

const p = new Promise((resolve, reject) => {
  resolve()
})

p.then(undefined, undefined)

  相关特征包括。

  代码修改为。

class Promise {
  ...

  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : () => {}
    onRejected = typeof onRejected === 'function' ? onRejected : () => {}
  }
}

参数为非函数时,为保证可被调用,暂时返回普通函数

then 方法

  原生Promise执行then方法。

const p1 = new Promise((resolve, reject) => {
  resolve(1)
})
p1.then(v => {
  console.log(v) // 1
})

const p2 = new Promise((resolve, reject) => {
  reject(2)
})
p2.then(undefined, v => {
  console.log(v) // 2
})

  相关特征包括。

  • 如果onFulfilledonRejected是一个函数,必须在promise被解决或被拒绝后调用,且promise值或原因作为第一个参数——「Promise/A+ 2.2.2 / 2.2.3」

  代码修改为。

then(onFulfilled, onRejected) {
  ...

  if (this[PromiseState] === FULFILLED) {
    onFulfilled(this[PromiseResult])
  }

  if (this[PromiseState] === REJECTED) {
    onRejected(this[PromiseResult])
  }
}

异步 executor

  目前代码并未完全符合「Promise/A+ 2.2.2 / 2.2.3」规范,例如executor为异步情况时,还会存在一些问题。

const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1)
  }, 1000)
})

p.then(v => {
  console.log(v)
})

控制台没有打印任何内容

  为什么呢?

  实例p在创建完成后,还处在等待态。紧接着执行then方法,then方法内部没有等待态相关逻辑,也就没有任何操作。1sresolve执行,也仅仅是将p状态修改为解决态。

  如何解决呢?

  可以在等待态就保存onFulfilledonRejected函数,在resolve修改状态时再执行。

  代码优化为。

class Promise {
  ...

  #onFulfilledCallBack = undefined
  #onRejectedCallBack = undefined
  #resolve = value => {
    if (this[PromiseState] === PENDING) {
      ...

      this.#onFulfilledCallBack?.(this[PromiseResult])
    }
  }
  #reject = reason => {
    if (this[PromiseState] === PENDING) {
      ...

      this.#onRejectedCallBack?.(this[PromiseResult])
    }
  }

  ...

  then(onFulfilled, onRejected) {
    ...

    if (this[PromiseState] === PENDING) {
      this.#onFulfilledCallBack = onFulfilled
      this.#onRejectedCallBack = onRejected
    }
  }
}

?.ES2020规范中 proposal-optional-chaining 可选链操作符

多次调用 then

  原生Promise多次调用then方法。

const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve()
  }, 1000)
})

p.then(() => {
  console.log(1) // 1
})

p.then(() => {
  console.log(2) // 2
})

p.then(() => {
  console.log(3) // 3
})

  相关特征包括。

  • then方法函数参数按语法顺序执行
  • 同一promisethen方法可能被多次调用——「Promise/A+ 2.2.6」

  代码优化如下,注意为了保证顺序,两数组内函数都是先进先出。

class Promise {
  ...

  #onFulfilledCallBacks = []
  #onRejectedCallBacks = []
  #resolve = value => {
    if (this[PromiseState] === PENDING) {
      ...

      while (this.#onFulfilledCallBacks.length) {
        this.#onFulfilledCallBacks.shift()(this[PromiseResult])
      }
    }
  }
  #reject = reason => {
    if (this[PromiseState] === PENDING) {
      ...

      while (this.#onRejectedCallBacks.length) {
        this.#onRejectedCallBacks.shift()(this[PromiseResult])
      }
    }
  }

  ...

  then(onFulfilled, onRejected) {
    ...

    if (this[PromiseState] === PENDING) {
      this.#onFulfilledCallBacks.push(onFulfilled)
      this.#onRejectedCallBacks.push(onRejected)
    }
  }
}

返回 promise

  原生Promise返回值。

const p = new Promise(() => {})

p.then()
// {
//   [[PromiseState]]: 'pending',
//   [[PromiseResult]]: undefined,
// }

  相关特征包括。

  代码暂时修改为。

then(onFulfilled, onRejected) {
  ...

  if (this[PromiseState] === PENDING) {
    ...
  }

  const promise = new Promise(() => {})

  return promise
}

函数参数返回值

  原生Promise函数参数onFulfilled返回数值。

const p = new Promise((resolve, reject) => {
  resolve()
})

p.then(() => {
  return 1
})
// {
//   [[PromiseState]]: 'fulfilled',
//   [[PromiseResult]]: 1,
// }

  相关特征包括。

  何为promise解决程序呢?

  「Promise/A+ 2.3」叙述是一个抽象操作,可表示为[[Resolve]](promise, x)。其中主要根据x类型,决定新promise的状态和结果。

  比如x不是一个对象或函数,例如数值,则新promise状态将确定为解决态,结果为x,即用x解决promise

// {
//   [[PromiseState]]: 'fulfilled',
//   [[PromiseResult]]: x,
// }

  那么如何在onFulfilledonRejected返回数值x时,又修改新promise状态和结果呢?

then(onFulfilled, onRejected) {
  ...

  if (this[PromiseState] === FULFILLED) {
    const x = onFulfilled(this[PromiseResult])
  }

  ...

  const promise = new Promise(() => {})

  return promise
}

  你可能想到了。

then(onFulfilled, onRejected) {
  ...

  const promise = new Promise(() => {})

  if (this[PromiseState] === FULFILLED) {
    const x = onFulfilled(this[PromiseResult])

    promise.#resolve(x)
  }

  ...

  return promise
}

  可修改状态也符合规范,但个人认为此方式存在一些缺陷。

  将实例属性resolve私有化,就是为了限制外部访问。以promise.#resolve访问,而非this.#resolve,已经处于外部访问的范畴了,思路上不是很合理。

  还有更好的办法吗?

  我们知道在executor执行器上,resolvereject两个参数也可修改状态。

  如果将if语句体迁移至executor内部,有没有可能呢?答案是可以的。

then(onFulfilled, onRejected) {
  ...

  const promise = new Promise((resolve, reject) => {
    if (this[PromiseState] === FULFILLED) {
      const x = onFulfilled(this[PromiseResult])

      resolve(x)
    }

    ...
  })

  return promise
}

if语句体在executor外部时,同步执行。在executor内部时,也是同步执行

  相关特征完全实现了吗?并没有。

  若executor为异步情况时,还存在一些问题。

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve()
  }, 1000)
})

const p2 = p1.then(() => {
  return 2
})

setTimeout(() => {
  console.log(p2)
  // {
  //   [[PromiseState]]: 'pending',
  //   [[PromiseResult]]: undefined,
  // }
}, 2000)

控制台打印内容与原生不一致

  为什么呢?

  实例p1处于等待态,执行then方法将onFulfilled保存至数组中。1sresolve执行,p1状态修改为解决态,紧接着取出运行onFulfilledp2状态无任何变化。

  我们可以在onFulfilled执行时,对返回值x稍加处理。

const promise = new Promise((resolve, reject) => {
  ...

  if (this[PromiseState] === PENDING) {
    this.#onFulfilledCallBacks.push(() => {
      const x = onFulfilled(this[PromiseResult])

      resolve(x)
    })
    
    ...
  }
})

处理函数

  为了统一处理不同类型x值,并严格实现规范「Promise/A+ 2.3」中各子章节。

  修改代码并创建resolvePromise函数,参数暂时为xresolve

class Promise {
  ...

  then(onFulfilled, onRejected) {
    ...

    const promise = new Promise((resolve, reject) => {
      if (this[PromiseState] === FULFILLED) {
        const x = onFulfilled(this[PromiseResult])

        resolvePromise(x, resolve)
      }

      ...

      if (this[PromiseState] === PENDING) {
        this.#onFulfilledCallBacks.push(() => {
          const x = onFulfilled(this[PromiseResult])

          resolvePromise(x, resolve)
        })

        ...
      }
    })

    return promise
  }
}

function resolvePromise(x, resolve) {
  resolve(x)
}

  研读部分子章节。

2.3.1. If promise and x refer to the same object, reject promise with a TypeError as the reason.
2.3.2.2. If/when x is fulfilled, fulfill promise with the same value. 2.3.2.3. If/when x is rejected, reject promise with the same reason.

  可确认参数promisexresolvereject

const promise = new Promise((resolve, reject) => {
  if (this[PromiseState] === FULFILLED) {
    const x = onFulfilled(this[PromiseResult])

    resolvePromise(promise, x, resolve, reject)
  }

  ...

  if (this[PromiseState] === PENDING) {
    this.#onFulfilledCallBacks.push(() => {
      const x = onFulfilled(this[PromiseResult])

      resolvePromise(promise, x, resolve, reject)
    })
    
    ...
  }
})

function resolvePromise(promise, x, resolve, reject) {
  resolve(x)
}

抛出异常

  原生Promise抛出异常。

const p = new Promise((resolve, reject) => {
  resolve()
})

p.then(() => {
  throw new Error()
}).then(undefined, v => {
  console.log(v) // Error
})

  相关特征包括。

  代码优化为。

const promise = new Promise((resolve, reject) => {
  if (this[PromiseState] === FULFILLED) {
    try {
      const x = onFulfilled(this[PromiseResult])

      resolvePromise(promise, x, resolve, reject)
    } catch (e) {
      reject(e)
    }
  }

  ...
})

  类似地executor为异步情况时,也存在一些问题。

const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve()
  }, 1000)
})

p.then(() => {
  throw new Error() // Uncaught Error
}).then(undefined, v => {
  console.log(v)
})

未捕获到异常

  为什么呢?

  实例p处于等待态,执行then方法将onFulfilled保存。1sresolve执行,p状态修改为解决态,紧接着取出onFulfilled,运行内部抛出了异常。

  代码优化为。

const promise = new Promise((resolve, reject) => {
  ...

  if (this[PromiseState] === PENDING) {
    this.#onFulfilledCallBacks.push(() => {
      try {
        const x = onFulfilled(this[PromiseResult])

        resolvePromise(promise, x, resolve, reject)
      } catch (e) {
        reject(e)
      }
    })
    
    ...
  }
})

异步任务

  原生Promise异步。

const p = new Promise((resolve, reject) => {
  resolve(1)
})

console.log(2) // 2
p.then(v => {
  console.log(v) // 1
})
console.log(3) // 3

注意打印顺序2 3 1

  相关特征包括。

  代码简单修改为。

const queueTask = queueMicrotask

class Promise {
  ...

  then() {
    const promise = new Promise((resolve, reject) => {
      if (this[PromiseState] === FULFILLED) {
        try {
          queueTask(() => {
            const x = onFulfilled(this[PromiseResult])

            resolvePromise(promise, x, resolve, reject)
          })
        } catch (e) {
          reject(e)
        }
      }

      ...
    })

    return promise
  }
}

  注意try...catch并不能捕获到异步函数内抛出的异常,例如。

try {
  setTimeout(() => {
    throw new Error() // Uncaught Error
  })
} catch (error) {
  console.log(error)
}

  那如何优化呢?

  我们可以将全部try...catch语句放到异步函数中。

const promise = new Promise((resolve, reject) => {
  if (this[PromiseState] === FULFILLED) {
    queueTask(() => {
      try {
        const x = onFulfilled(this[PromiseResult])

        resolvePromise(promise, x, resolve, reject)
      } catch (e) {
        reject(e)
      }
    })
  }

  ...
})

  类似地executor为异步情况时,也存在一些问题。

const p = new Promise(resolve => {
  setTimeout(() => {
    console.log(1) // 1

    resolve(2)

    console.log(3) // 3
  }, 1000)
})

p.then(v => {
  console.log(v) // 2
})

打印顺序1 2 3(原生打印顺序1 3 2

  为什么呢?

  onFulfilled没有以异步形式运行。

  代码修改为。

const promise = new Promise((resolve, reject) => {
  ...

  if (this[PromiseState] === PENDING) {
    this.#onFulfilledCallBacks.push(() => {
      queueTask(() => {
        try {
          const x = onFulfilled(this[PromiseResult])

          resolvePromise(promise, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      })
    })
    
    ...
  }
})

  合并重复代码。

const promise = new Promise((resolve, reject) => {
  const resolved = () => {
    queueTask(() => {
      try {
        const x = onFulfilled(this[PromiseResult])

        resolvePromise(promise, x, resolve, reject)
      } catch (e) {
        reject(e)
      }
    })
  }
  const rejected = () => {
    queueTask(() => {
      try {
        const x = onRejected(this[PromiseResult])

        resolvePromise(promise, x, resolve, reject)
      } catch (e) {
        reject(e)
      }
    })
  }

  if (this[PromiseState] === FULFILLED) {
    resolved()
  }

  if (this[PromiseState] === REJECTED) {
    rejected()
  }

  if (this[PromiseState] === PENDING) {
    this.#onFulfilledCallBacks.push(resolved)
    this.#onRejectedCallBacks.push(rejected)
  }
})

参数优化

  原生Promise值穿透。

const p1 = new Promise((resolve, reject) => {
  resolve(1)
})
p1.then(undefined)
// {
//   [[PromiseState]]: 'fulfilled',
//   [[PromiseResult]]: 1,
// }

const p2 = new Promise((resolve, reject) => {
  reject(2)
})
p2.then(undefined, undefined)
// {
//   [[PromiseState]]: 'rejected',
//   [[PromiseResult]]: 2,
// }

  相关特征包括。

  • 如果onFulfilled不是一个函数且原promise被解决,新promise必须也被解决,且值与原promise相同——「Promise/A+ 2.2.7.3」
  • 如果onRejected不是一个函数且原promise被拒绝,新promise必须也被拒绝,且原因与原promise相同——「Promise/A+ 2.2.7.4」

  代码优化如下。

then(onFulfilled, onRejected) {
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
  onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }

  ...
}

注意throw抛出异常将被try...catch捕获,进而拒绝新promise

类型

  如何处理不同类型x呢?

  还是参考规范「Promise/A+ 2.3」各子章节,以优化resolvePromise处理函数。

循环引用

  原生Promise循环引用。

const p1 = new Promise((resolve, reject) => {
  resolve()
})

const p2 = p1.then(() => {
  return p2
})
// {
//   [[PromiseState]]: 'rejected',
//   [[PromiseResult]]: TypeError: Chaining cycle detected for promise #<Promise>,
// }

  相关特征包括。

  • 如果promisex引用同一对象,则拒绝promise,原因为一个TypeError——「Promise/A+ 2.3.1」

  代码优化为。

function resolvePromise(promise, x, resolve, reject) {
  if (promise === x) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  }

  resolve(x)
}

传递性

  原生Promise函数参数onFulfilled返回promise

const p1 = new Promise((resolve, reject) => {
  resolve()
})
const p2 = new Promise((resolve, reject) => {
  reject(1)
})

p1.then(() => {
  return p2
})
// {
//   [[PromiseState]]: 'rejected',
//   [[PromiseResult]]: 1,
// }

  相关特征包括。

  也就是promise状态与x始终都保持一致。

  可能会存在x初始为等待态,然后又转变为解决态或拒绝态。过程中两者状态始终一致,若x状态转变,promise状态也将转变。

  那如何知道x状态转变呢?答案就是then方法。

x.then(onFulfilled, onRejected)

  x转变为解决态时将运行onFulfilled,转变为拒绝态时将运行onRejected

  那我们就可在onFulfilledonRejected内部去修改promise状态。

  代码优化为。

function resolvePromise(promise, x, resolve, reject) {
  ...

  if (x instanceof Promise) {
    x.then(value => {
      resolve(value)
    }, reason => {
      reject(reason)
    })
  } else {
    resolve(x)
  }
}

  简化为。

function resolvePromise(promise, x, resolve, reject) {
  ...

  if (x instanceof Promise) {
    x.then(resolve, reject)
  } else {
    resolve(x)
  }
}

广义对象

  何为广义对象呢?

  能添加属性或方法的变量,都称之为广义上的对象,例如数组、函数等。

  创建isObject工具函数,更多参考 lodash.isObject

function isObject(value) {
  const type = typeof value

  return value !== null && (type === 'object' || type === 'function')
}

  然后阅读规范「Promise/A+ 2.3.3」小节,省略部分暂时不考虑。

  • 如果x是一个对象或函数(广义对象)
    • thenx.then
    • 如果获取x.then导致抛出了一个异常e,用e作为原因拒绝promise
    • 如果then是一个函数,用x作为this调用它,且包含两个参数,分别为resolvePromiserejectPromise
      • 如果resolvePromise用一个值y调用,运行[[Resolve]](promise, y)
      • 如果rejectPromise用一个原因r调用,用r拒绝promise
      • ...
      • 如果调用then抛出了一个异常e
        • ...
        • 否则用e作为作为原因拒绝promise
    • 如果then不是一个函数,用x解决promise

  转译为代码。

function resolvePromise(promise, x, resolve, reject) {
  ...

  if (x instanceof Promise) {
    ...
  } else {
    if (isObject(x)) {
      var then

      try {
        then = x.then
      } catch (e) {
        reject(e)
      }

      if (typeof then === 'function') {
        try {
          then.call(
            x,
            y => {
              resolvePromise(promise, y, resolve, reject)
            },
            r => {
              reject(r)
            }
          )
        } catch (e) {
          reject(e)
        }
      } else {
        resolve(x)
      }
    } else {
      resolve(x)
    }
  }
}

  规范中运行[[Resolve]](promise, y),即递归resolvePromise,为什么呢?

  原因在于y值可能还是promise或者广义对象等等。

  我们来看一个原生Promise示例。

const p = new Promise(resolve => {
  resolve()
})
const thenable1 = {
  then(reslove) {
    reslove(1)
  },
}
const thenable2 = {
  then(resolve) {
    resolve(thenable1)
  },
}

p.then(() => {
  return thenable2
})
// {
//   [[PromiseState]]: 'fulfilled',
//   [[PromiseResult]]: 1,
// }

优先级

  以下为刚才省略的部分。

  • 如果then是一个函数...
    • ...
    • ...
    • 如果resolvePromiserejectPromise都被调用,或者对其中一个多次调用,那么第一次调用优先,以后的调用都会被忽略
    • 如果调用then抛出了...
      • 如果resolvePromiserejectPromise已经被调用,则忽略它
      • ...

  为了限制哪种情况呢?

  还是来看一个原生Promise示例。

const p = new Promise(resolve => {
  resolve()
})
const thenable1 = {
  then(reslove) {
    setTimeout(() => {
      reslove(2)
    }, 0)
  },
}
const thenable2 = {
  then(resolve) {
    resolve(thenable1)
    resolve(1)
  },
}

p.then(() => {
  return thenable2
})
// {
//   [[PromiseState]]: 'fulfilled',
//   [[PromiseResult]]: 2,
// }

  代码如何优化呢?

  我们可定义布尔变量called,标记是否运行参数resolvePromiserejectPromise。然后在第一次运行时将called修改为true,而以后的都会return被忽略。

if (typeof then === 'function') {
  var called = false

  try {
    then.call(
      x,
      y => {
        if (called) return
        called = true

        resolvePromise(promise, y, resolve, reject)
      },
      r => {
        if (called) return
        called = true

        reject(r)
      }
    )
  } catch (e) {
    if (called) return
    called = true

    reject(e)
  }
}

thenable

  规范「Promise/A+ 1.1」小结陈述了。

  promise是一个对象或函数(广义对象),存在then方法且行为符合规范。

  第三方Promise库、原生Promise以及我们手写版本Promise,创建的promise实例,其实都是标准的promise类型。

  而代码中x instanceof Promise语句,检验是否为promise类型,就有问题了。例如x被第三方库创建,也是标准promise类型,但是并不会运行if语句体,而是错误地运行else语句体。

function resolvePromise(promise, x, resolve, reject) {
  ...

  if (x instanceof Promise) {
    ...
  } else {
    ...
  }
}

  还有方法可确定xpromise类型吗?答案是没有。

  怎么办呢?

  既然无法检验promise类型,那就退而求其次,检验类似promise类型的,即鸭式辩型。

鸭子类型(duck typing),也叫鸭式辩型,一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子

  规范「Promise/A+ 1.2」提出了thenable类型,即定义了then方法的对象或函数。

{
  then() {
    ...
  },
}

thenablepromise的鸭子类型

  检验是否为promise类型,则降级为检验是否为thenable类型。

  代码修改为。

function resolvePromise(promise, x, resolve, reject) {
  if (promise === x) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  }

  if (isObject(x)) {
    var then

    try {
      then = x.then
    } catch (e) {
      reject(e)
    }

    if (typeof then === 'function') {
      var called = false

      try {
        then.call(
          x,
          y => {
            if (called) return
            called = true

            resolvePromise(promise, y, resolve, reject)
          },
          r => {
            if (called) return
            called = true

            reject(r)
          }
        )
      } catch (e) {
        if (called) return
        called = true

        reject(e)
      }
    } else {
      resolve(x)
    }
  } else {
    resolve(x)
  }
}

测试

  安装官方测试用例 promises-aplus-tests

npm i promises-aplus-tests -D

  promise代码中新增以下。

// promise.js
class Promise {
  ...
}

Promise.deferred = () => {
  const result = {}

  result.promise = new Promise((resolve, reject) => {
    result.resolve = resolve
    result.reject = reject
  })

  return result
}

module.exports = Promise

  新增测试命令。

// package.json
{
  ...
  "scripts": {
    "test": "promises-aplus-tests promise.js"
  },
  ...
  "devDependencies": {
    "promises-aplus-tests": "^2.1.2"
  }
}

  运行npm run test

在这里插入图片描述

小结

  全文共计两万五千字有余,参考Promise/A+规范手写了then方法和promise解决程序。

  相关代码可参考 promise-coding 仓库,支持node和浏览器环境测试。

  如何手写Promise到此就结束了。

扩展

  学有余力或意犹未尽的伙伴们。

  贴出两个代码片段,可在原生Promise与手写Promise环境下运行。

// 1
new Promise(resolve => {
  resolve(Promise.resolve())
}).then(() => {
  console.log(3)
})

Promise.resolve()
  .then(() => {
    console.log(1)
  })
  .then(() => {
    console.log(2)
  })
  .then(() => {
    console.log(4)
  })

// 2
Promise.resolve()
  .then(() => {
    console.log(0)

    return Promise.resolve()
  })
  .then(() => {
    console.log(4)
  })

Promise.resolve()
  .then(() => {
    console.log(1)
  })
  .then(() => {
    console.log(2)
  })
  .then(() => {
    console.log(3)
  })
  .then(() => {
    console.log(5)
  })
  .then(() => {
    console.log(6)
  })

  看看能否分析出两者之间的细微差异?答案是不能。

  更多请持续关注更文,或在参考链接中探索一二。

参考

🎉 写在最后

🍻伙伴们,如果你已经看到了这里,觉得这篇文章有帮助到你的话不妨点赞👍或 Star ✨支持一下哦!

手动码字,如有错误,欢迎在评论区指正💬~

你的支持就是我更新的最大动力💪~

GitHub / GiteeGitHub Pages掘金CSDN 同步更新,欢迎关注😉~