一、Promise 的用法

要想实现一个 Promise / A+ 规范的 Promise,首先要弄清楚如何使用 Promise。
Promise() 构造函数接收一个回调函数:executor ,其中 executor 是一个双参函数,包含 resolve 和 reject,用于返回一个新的 Promise 对象,并立即执行 executor
Promise的实现会立即执行 executor,并传入resolvereject函数(Promise构造器将会在返回新对象之前executor)。
resolvereject函数被调用时,它们分别对 promise 执行resolverejectexecutor通常会触发一些异步运算,一旦运算成功完成,则resolve掉这个promise,如果出错则reject掉。如果executor函数执行时抛出异常,promise状态会变为rejectedexecutor的返回值也会被忽略。

  1. new Promise((resolve, reject) => {
  2. // do something
  3. try {
  4. resolve(value)
  5. } catch (reason) {
  6. reject(reason)
  7. }
  8. }).then(value=> {
  9. // do something
  10. }).catch(reason => {
  11. // do something
  12. })

Promise 有以下几个静态方法:

  • Promise.all(iterable):返回一个新的 promise 对象,该promise对象在 iterable 参数对象里所有的 promise 对象都成功的时候才会触发成功,一旦有任何一个 iterable 里面的 promise 对象失败则立即触发该 promise 对象的失败。
  • Promise.allSettled(interable):等到所有 interable 的 Promise 都被敲定(settled),即状态为 fulfilled 或 rejected 时,返回一个新的 Promise。该 Promise 的 value 是一个数组,用于存储所有 Promise 的执行结果。
  • Promise.any(interable):有一个 Promise 成功,就返回那个成功的值。
  • Promise.race(interable):任意一个 Promise 先成功或失败时,返回这个 Promise
  • Promise.resolve(value)
  • Promise.reject(reason)

    二、Promise 的特点(Promise / A+ 规范)

    1、Promise 的三种状态

    根据 Promise / A+ 规范 2.1 章节的描述,Promise 作为一个状态机,他有三种状态,分别是 pending (等待态),fulfilled(成功态),rejected(失败态)
  1. 当 Promise 处于 pending 状态时:
    1. Promise 可以转化为 fulfilled 状态和 rejected 状态
  2. 当 Promise 处于 fulfilled 状态时:
    1. Promise 不可以转换成别的状态
    2. Promise 必须有一个不可变的返回值:value
  3. 当 Promise 处于 rejected 状态时:

    1. Promise 不可以转换成别的状态
    2. Promise 必须有一个不可变的返回原因:reason

      1. class Promise {
      2. constructor(executor) {
      3. this.state = 'pending'
      4. this.value = void 0
      5. this.reason = void 0
      6. const resolve = value => {
      7. if(this.state === 'pending') {
      8. this.state = 'fulfilled'
      9. this.value = value
      10. }
      11. }
      12. const reject = reason => {
      13. if(this.state === 'pending') {
      14. this.state = 'rejected'
      15. this.reason = reason
      16. }
      17. }
      18. try {
      19. executor(resolve, reject)
      20. } catch(e) {
      21. reject(e)
      22. }
      23. }
      24. }

      2、then 方法

      Promise 除了有 pending、fulfilled、rejected 这三种状态的特点外,还有一个特点是,Promise 是 thenable 的,即 then 方法可以被链式调用。

      1. const p = new Promese()
      2. p.then()
      3. p.then()

      并且 Promise 必须提供一个 then 方法访问当前或者最终成功的结果或者失败原因。
      根据 Promise / A+ 规范 的定义,then 方法有两个参数:onFulfilled 和 onRejected。按照规范描述,其特点如下:

  4. onFulfilledonRejected 都为可选的参数时:

    1. 如果onFulfilled 不是函数,则它将被忽略。
    2. 如果onRejected不是函数,则它将被忽略。
  5. 如果 onFulfilled 是函数:
    1. 此函数在 promise成功后(fulfilled)被调用,并把 promise的成功值(value)作为它的第一个参数。
    2. promise成功(fulfilled)之前一定不能提前被调用。
    3. 该函数只执行一次
  6. 如果 onRejected 是函数:
    1. 此函数在 promise失败(rejected)时被调用, 并且把 promise的失败原因(reason)当成第一个参数。
    2. promise失败(rejected)之前一定不能提前被调用。
    3. 该函数只执行一次。
  7. onFulfilledonRejected只有在 执行上下文 堆栈仅包含平台代码时才可被调用
  8. onFulfilledonRejected必须被作为函数调用 (即没有this值)。
  9. then方法可以被同一个promise多次调用
    1. promise成功时, 所有 onFulfilled 回调函数需按照最原始的then顺序来调用。
    2. promise失败时,所有各自的onRejected回调都必须按照其对then的原始调用顺序执行。
  10. then必须返回一个promise:promise2 = promise1.then(onFulfilled, onRejected)
    1. 如果 onFulfilled或者 onRejected返回一个值 x,则运行下面的 Promise 解决过程: [[Resolve]](promise2, x)
    2. 如果onFulfilled或 onRejected抛出一个异常e,promise2 必须被拒绝(rejected)并把e当作失败的原因(reason)
    3. 如果onFulfilled不是一个函数且promise1成功执行(fulfilled),则 promise2将会接收promise1传递下来的成功(fulfilled)的值
    4. 如果onRejected不是一个函数,并且 promise1已经失败了(rejected),则必须以同promise1相同的失败(rejected)的原因(reason)传递到promise2

官方的描述有点啰嗦,总结的思维导图如下:
4. 实现 Promise/A+ 规范的 Promise - 图1
为了搞懂上述逻辑,我们需要熟悉一下几个概念:

  • promise2:因为 then 函数需要返回一个新的 Promise 对象,所以我们在 then 方法的实现里,实例化了一个 Promise对象 promise2
  • x:then 方法的返回值,在 then 方法内及 onFulfilled 和 onRejected 的返回值
  • resolvePromise:Promise 的处理方法。

Then 方法的主要逻辑如下:

  • Then 方法有两个参数:onFulfilled 和 onRejected,并判断二者不是 function 的情况
  • Then 方法返回值为一个 Promise 对象:Promise2
  • 如果 Promise 处于 pending 态,则在异步队列中添加该异步方法
  • 否则执行 onFulfilled 和 onRejected,并将 promise2 与返回值 x 做校验(resolvePromise)

根据上述描述,我们需要在 Promise 类上添加两个队列属性,分别用于存放成功与失败的执行函数

  1. class Promise {
  2. constructor(executor) {
  3. this.state = 'pending'
  4. this.value = void 0
  5. this.reason = void 0
  6. this.onFulfilledCallbacks = []
  7. this.onRejectedCallbacks = []
  8. const resolve = value => {
  9. if(this.state === 'pending') {
  10. this.state = 'fulfilled'
  11. this.value = value
  12. // 执行所有成功队列的方法
  13. this.onFulfilledCallbacks.forEach(fn => fn())
  14. }
  15. }
  16. const reject = reason => {
  17. if(this.state === 'pending') {
  18. this.state = 'fulfilled'
  19. this.reason = reason
  20. // 执行所有失败队列里的方法
  21. this.onRejectedCallbacks.forEach(fn => fn())
  22. }
  23. }
  24. try {
  25. executor(resolve, reject)
  26. } catch(e) {
  27. reject(e)
  28. }
  29. }
  30. then(onFulfilled, onRejected) {
  31. onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
  32. onRejected = typeof onRejected === 'function' ? onFulfilled : reason => reason
  33. const promise2 = new Promise((resolve, reject) => {
  34. if(this.state === 'pending') {
  35. // 将 onFulfilled 和 onRejected 添加到队列中
  36. this.onFulfilledCallbacks.push(_ => {
  37. const x = onFulfilled(this.value)
  38. resolvePromise(promise2, x, resolve, reject)
  39. })
  40. this.onRejectedCallbacks.push(_ => {
  41. const x = onRejected(this.reason)
  42. resolvePromise(promise2, x, resolve, reject)
  43. })
  44. }
  45. if(this.state === 'fulfilled') {
  46. const x = onFulfilled(this.value)
  47. resolvePromise(promise2, x, resolve, reject)
  48. }
  49. if(this.state === 'rejected') {
  50. const x = onRejected(this.reason)
  51. resolvePromise(promise2, x, resolve, reject)
  52. }
  53. })
  54. return promise2
  55. }
  56. }

3、Promise 的处理过程

上述代码已经基本实现了一个 Promise,但是我们没有实现 resolvePromise 方法。Promise / A+ 规范详细的描述了 Promise 的处理过程,因此我们依据 Promise / A+ 规范中的 2.3 章节来实现 resolvePromise。具体规范如下:
Promise处理过程是一个抽象的操作,其需输入一个 promise 和一个值 x:
我们表示为[[Resolve]](promise, x)
如果x 是一个 thenable 对象,处理程序将以这个promise对象的 then 返回值继续传递下去,如果x是一个普通值,则以成功的回调传递给下去。
这种 thenable 的特性使得 Promise 的实现更具有通用性:只要其暴露出一个遵循 Promise/A+ 协议的 then 方法即可;这同时也使遵循 Promise/A+ 规范的代码可以与那些不太规范但可用的实现能良好兼容。
运行 [[Resolve]](promise, x), 需要遵循以下几个步骤:

  1. 如果 promisex 是相同的, 则报 TypeError 错误。
  2. 如果 x是一个 promise对象, 则使 promise 接受 x 的状态 [3.4]
    1. 如果 x处于等待状态,promise需保持为等待态直至 x 被执行或拒绝
    2. 如果 x 处于执行态,用相同的值执行 promise
    3. 如果 x 处于拒绝态,用相同的据因拒绝 promise
  3. x是对象或者函数时:
    1. x.then 赋值给 then方法 [3.5]
    2. 如果在获取属性x.then的过程中导致抛出异常e,则拒绝promise并用e作为拒绝原因。
    3. 如果then是函数,则将x作为函数的作用域的this被绑定并调用。传递两个回调函数作为参数,第一个参数叫做resolvePromise,第二个参数叫做rejectPromise
      1. 如果 resolvePromise以值 y为参数被调用,则运行 [[Resolve]](promise, y)
      2. 如果rejectPromise以据因r为参数被调用,则以据因r拒绝promise
      3. 如果resolvePromiserejectPromise均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
      4. 如果调用then方法抛出了异常e:
        1. 如果resolvePromiserejectPromise已经被调用,则忽略它
        2. 否则以e为拒绝promise的原因
    4. 如果then不是函数,以x为参数执行 promise
  4. 如果x不是对象或者函数,以x为参数执行 promise

如果 promise 被一个 thenable 解决,且该 thenable 参与一个 thenable 循环链,那么 [Resolve]的递归性质最终导致 [Resolve]被再次调用,上述算法将导致无限递归。支持实现此类递归检测,并以一个 TypeError类型的值作为原因拒绝 promise,但此类检测不是必须的。 [3.6]

  1. function resolvePromise(promise2, x, resolve, reject) {
  2. // 如果 x 和 promise2 相等,则 reject TypeError 错误
  3. if (x === promise2) {
  4. return reject(new TypeError('Chaining cycle detected for promise'))
  5. }
  6. // 如果 x 是 object 或 function
  7. let called = false
  8. if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
  9. try {
  10. const then = x.then
  11. if (typeof then === 'function') {
  12. then.call(x, y => {
  13. ifcalled return
  14. called = true
  15. resolvePromise(promise2, y, resolve, reject)
  16. }, e => {
  17. if (called) return
  18. called = true
  19. reject(e)
  20. })
  21. } else {
  22. resolve(x)
  23. }
  24. } catch(e) {
  25. if (called) return
  26. called = true
  27. reject(e)
  28. }
  29. } else {
  30. // 如果 x 是 Promise,则 resolve
  31. resolve(x)
  32. }
  33. }

三、校验实现的 Promise 是否符合 Promise / A+ 规范:

promises-aplus-tests

四、其他 Promise 静态成员的实现

1、Promise.resolve

  1. Promise.resolve = function(value) {
  2. return new Promise((resolve, reject) => {
  3. resolve(value)
  4. })
  5. }

2、Promise.reject

  1. Promise.reject = function(reason) {
  2. return new Promise((resolve, reject) => {
  3. reject(reason)
  4. })
  5. }

3、Promise.all

  1. Promise.all = function(promises) {
  2. const queue= []
  3. let i = 0
  4. cosnt processData = (index, data) => {
  5. queue[index] = data
  6. i++
  7. if (i === queue.length) {
  8. resolve(data)
  9. }
  10. }
  11. return new Promise((resolve, reject) => {
  12. for (let i=0; i<promises.length, i++) {
  13. promises[i].then(data => {
  14. process(i, data)
  15. }, reject)
  16. }
  17. })
  18. }

4、Promise.race

  1. Promise.race = function(promises) {
  2. return new Priomise((resolve, reject) => {
  3. for(let i=0; i<promises.length, i++) {
  4. promises[i].then(resolve, reject)
  5. }
  6. })
  7. }

5、Promise.any

  1. Promise.any = function(promises) {
  2. return new Promise((resolve, reject) => {
  3. let n = promises.length
  4. const errs = []
  5. if (n === 0) return reject(new AggregateError('All promises were rejected'))
  6. for(let i=0; i<n; i++) {
  7. promises[i].then(value => resolve(value), err => {
  8. n--
  9. errs.push(err)
  10. if(n === 0) return reject(new AggregateError(err))
  11. })
  12. }
  13. })
  14. }

6、Promise.allSettled

  1. Promise.allSettled = function(promises) {
  2. return new Promsie((resolve, reject) => {
  3. let n = promises.length
  4. const data = []
  5. for (let i=0; i<n; i++) {
  6. promises[i].then(value => {
  7. data[i] = { status: 'fulfilled', value }
  8. }, reason => {
  9. data[i] = { status: 'rejected', reason }
  10. }).finally(_ => {
  11. if(!--n) {
  12. resolve(data)
  13. }
  14. })
  15. }
  16. })
  17. }

五、最终代码

Promises/A+ (promisesaplus.com)
使用 Promise - JavaScript | MDN (mozilla.org)
Promise - JavaScript | MDN (mozilla.org)