使用 queueMicrotask API 实现符合 Promise/A+ 规范的 Promise

·  阅读 522
使用 queueMicrotask API 实现符合 Promise/A+ 规范的 Promise

一、规范

首先看看 Promise/A+ 规范要求:

Promise 代表着异步操作的最终结果。与 promise 进行交互的主要方式是通过then方法, 该方法通过注册回调以接收 promise 的最终值或 promise 未完成的原因。

  1. promise 状态

    pormise 必须是以下三个状态之一:pending,fulfilled,rejected。

    1. 当 promise 处于 pending 状态时:
      1. 可以转换到 fulfilled 或 rejected 状态。
    2. 当 promise 处于 fulfilled 状态时:
      1. 不能转换到其他状态。
      2. 必须有一个 value,并且不能改变。
    3. 当 promise 处于 rejected 状态时:
      1. 不能转换到其他状态。
      2. 必须有 reason ,并且不能改变。
  2. then 方法

    promise 必须提供一个 then 方法,能由此去访问当前或最终的 value 或者 reason。

    pormise 的 then 方法, 接受两个参数 promise.then(onFulfilled, onRejected)

    1. onFulfilledonRejected 都是可选参数。

      1. 如果 onFulfilled 不是函数,则忽略。
      2. 如果 onRejected 不是函数,则忽略。
    2. 如果 onFulfilled 是一个函数:

      1. 它必须在 promisefulfilled 后,以 promisevalue 作为第一个参数调用。
      2. 它不能在 promisefulfilled 之前调用。
      3. 它不能被调用多次。
    3. 如果 onRejected 是一个函数:

      1. 它必须在 promiserejected 后,以 promisereason 作为第一个参数调用。
      2. 它不能能在 promiserejected 之前调用。
      3. 它不能被调用多次。
    4. 在 execution context 栈(执行上下文栈)只包含引擎代码之前, onFulfilled 或者 onRejected 不能被调用.

    5. onFulfilled 或者 onRejected 必须以函数形式调用(即不能有this值)

    6. then 方法可以被同一个 promise 调用多次。

      1. promise 处于 fulfilled 状态, 所有自己的 onFulfilled 回调函数,必须要按照 then 注册的顺序被调用。
      2. promise 处于 rejected 状态, 所有自己的 onRejected 回调函数,必须要按照 then 注册的顺序被调用。
    7. then 方法必须要返回 promise

      promise2 = promise1.then(onFulfilled, onRejected);

      1. 如果 onFulfilled 或者 onRejected 返回一个值 x ,则执行 Promise Resolution Procedure [[Resolve]](promise2, x).
      2. 如果 onFulfilled 或者 onRejected 抛出异常 epromise2 必须以 e 作为 reason ,转到 rejected 状态。
      3. 如果 onFulfilled 不是函数,并且 promise1 处于 fulfilled 状态 ,则 promise2 必须以与 promise1 同样的 value 被 fulfilled .
      4. 如果 onRejected 不是函数,并且 promise1 处于 rejected 状态 ,则 promise2 必须以与 promise1 同样的 reason 被 rejected .
  3. Promise Resolution Procedure

    Promise Resolution Procedure 是一个抽象操作。它以一个 promise 和一个 value 作为输入,记作:[[Resolve]](promise, x) 。 如果 x 是一个 thenable , 它会尝试让 promise 变成与 x 的一样状态 ,前提 x 是一个类似的 promise 对象。否则,它会让 promise 以 x 作为 value 转为 fulfilled 状态。

    这种对 thenables 的处理允许不同的 promise 进行互操作,只要它们暴露一个符合 Promises/A+ 的 then 方法。它还允许 Promises/A+ 实现使用合理的 then 方法“同化”不一致的实现。

    [[Resolve]](promise, x) 执行以下步骤:

    1. 如果 promisex 引用的是同一个对象,则以一个 TypeError 作为 reason 让 promise 转为 rejeted 状态。

    2. 如果 x 也是一个 promise ,则让 promise 接受它的状态

      1. 如果 x 处于 pending 状态,promise 必须保持 pending 状态,直到 x 变成 fulfilled 或者 rejected 状态,promise 才同步改变。
      2. 如果或者当 x 处于 fulfilled 状态, 以同样的 value 让 promise 也变成 fulfilled 状态。
      3. 如果或者当 x 处于 rejected 状态, 以同样的 reason 让 promise 也变成 rejected 状态。
    3. 如果 x 是一个对象或者函数。

      1. then 等于 x.then.

      2. 如果读取 x.then 抛出异常 e , 以 e 作为 reason 让 promise 变成 rejected 状态。

      3. 如果 then 是一个函数,以 x 作为 this 调用它,传入第一个参数 resolvePromise , 第二个参数 rejectPromise

        1. 如果 resolvePromise 被传入 y 调用, 则执行 [[Resolve]](promise, y)

        2. 如果 rejectedPromise 被传入 r 调用,则用,r 作为 reason 让 promise 变成 rejected 状态

        3. 如果 resolvePromiserejectPromise 都被调用了,或者被调用多次了。只有第一次调用生效,其余会被忽略。

        4. 如果调用 then 抛出异常 e ,

          1. 如果 resolvepromiserejectPromise 已经被调用过了,则忽略它。
          2. 否则, 以 e 作为 reason 让 promise 变成 rejected 状态。
      4. 如果 then 不是一个函数,以 x 作为 value 让 promise 变成 fulfilled 状态。

    4. 如果 x 不是对象或函数, 以 x 作为 value 让 promise 变成 fulfilled 状态。

如果一个 promise 被一个循环的 thenable 链中的对象 resolved,而 [[Resolve]](promise, thenable) 的递归性质又使得其被再次调用,根据上述的算法将会陷入无限递归之中。算法虽不强制要求,但也鼓励实现者检测这样的递归是否存在,并且以 TypeError 作为 reason 拒绝 promise。

二、实现

  1. 使用 class 实现 Promise 类
  2. 使用 class 私有字段
  3. 使用 queueMicrotask取代setTimeout实现异步微任务
// promise.js
/* promise/A+ 规范  */
class Promise {
  static #FULFILLED = 'fulfilled'
  static #REJECTED = 'rejected'
  static #PENDING = 'pending'

  static #resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {
      throw new TypeError('Chaining cycle detected for promise')
    }
    if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
      let called
      try {
        const then = x.then
        if (typeof then !== 'function') resolve(x)
        else {
          then.call(
            x,
            (value) => {
              if (called) return
              called = true
              Promise.#resolvePromise(promise2, value, resolve, reject)
            },
            (reason) => {
              if (called) return
              called = true
              reject(reason)
            }
          )
        }
      } catch (e) {
        if (called) return
        called = true
        reject(e)
      }
    } else {
      resolve(x)
    }
  }

  #state = Promise.#PENDING
  #result = null
  #onResolveCallbacks = []
  #onRejectCallbacks = []

  constructor(executor) {
    if (typeof executor !== 'function') {
      return new TypeError(`Promise resolver ${executor} is not a function`)
    }
    try {
      executor(this.#resolve.bind(this), this.#reject.bind(this))
    } catch (e) {
      this.#reject(e)
    }
  }

  #resolve(value) {
    if (this.#state === Promise.#PENDING) {
      this.#state = Promise.#FULFILLED
      this.#result = value
      this.#onResolveCallbacks.forEach((cb) => cb())
    }
  }
  #reject(reason) {
    if (this.#state === Promise.#PENDING) {
      this.#state = Promise.#REJECTED
      this.#result = reason
      this.#onRejectCallbacks.forEach((cb) => cb())
    }
  }
  then(onFulfilled, onRejected) {
    if (typeof onFulfilled !== 'function') {
      onFulfilled = (value) => value
    }
    if (typeof onRejected !== 'function') {
      onRejected = (reason) => {
        throw reason
      }
    }
    const promise2 = new Promise((resolve, reject) => {
      if (this.#state === Promise.#PENDING) {
        this.#onResolveCallbacks.push(() => {
          queueMicrotask(() => {
            try {
              const x = onFulfilled(this.#result)
              Promise.#resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          })
        })
        this.#onRejectCallbacks.push(() => {
          queueMicrotask(() => {
            try {
              const x = onRejected(this.#result)
              Promise.#resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          })
        })
      }
      if (this.#state === Promise.#FULFILLED) {
        queueMicrotask(() => {
          try {
            const x = onFulfilled(this.#result)
            Promise.#resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }
      if (this.#state === Promise.#REJECTED) {
        queueMicrotask(() => {
          try {
            const x = onRejected(this.#result)
            Promise.#resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }
    })
    return promise2
  }
}

// 实现以下功能,并且执行 npx promises-aplus-tests 
Promise.defer = Promise.deferred = function () {
  let dfd = {}
  dfd.promise = new Promise((resolve, reject) => {
    dfd.resolve = resolve
    dfd.reject = reject
  })
  return dfd
}

module.exports = Promise
复制代码

三、验证

  1. 将文件保存为 promise.js
  2. 在目录执行以下命令验证
$ npx promises-aplus-tests promise.js
复制代码

参考:

  1. 中文翻译:juejin.cn/post/700177…
分类:
前端
收藏成功!
已添加到「」, 点击更改