Promise/A+规范

59 阅读4分钟

Promise/A+规范的背景

Promise/A+规范 是一个开源社区制定的标准,旨在为 JavaScript中的Promise提供一致、可靠的实现指南(标准)。它起源于早期的Promise实现(如Promise/A、Promise/B等),为了解决不同库之间的互操作性问题(早期各种三方库都各自实现了自己的Promise或类似机制),社区在2013年制定了Promise/A+规范,作为一个简洁、明确的标准。

Promise/A+规范的核心术语

Promise/A+规范使用了一些关键术语:

  • Promise:一个具有then方法的对象或函数,行为符合Promise/A+规范。
  • thenable:一个定义了then的对象或函数(可以被Promise识别并处理)。
  • value:Promise兑现(fulfilled)时返回的任意合法JavaScript值(包括undefined、thenable或Promise)。
  • reason:Promise拒绝(rejected)时提供的错误原因。
  • exception:在Promise执行过程中抛出的异常。

Promise的状态

Promise/A+规范定义了Promise的三种状态(与JavaScript Promise一致):

  1. Pending(待定) :初始状态Promise尚未完成。
  2. Fulfilled(已兑现) :Promise已成功完成,携带一个value。
  3. Rejected(已拒绝) :Promise已失败,携带一个reason。

状态规则:

  • Promise必须处于三种状态之一。
  • 状态只能从Pending转换为FulfilledRejected一旦改变不可逆。
  • 如果状态为Fulfilled,必须有一个不可变的value
  • 如果状态为Rejected,必须有一个不可变的reason

then方法的核心要求

Promise/A+规范的核心是then方法的行为定义。then方法时Promise的主要接口,用于注册回调函数处理Fulfilled或Rejected状态。

then方法的签名

promise.then(onFulfilled, onRejected);
  • onFulfilled:当 Promise 变为 Fulfilled 时调用的函数,接收 value 作为参数。
  • onRejected:当 Promise 变为 Rejected 时调用的函数,接收 reason 作为参数。
  • 两个参数都是可选的(可以是 undefined 或非函数)。

then方法的要求

  1. 返回新的Promise:

    • promise.then(onFulfilled, onRejected)必须返回一个新的Promise(成为promise2)
    • 这样支持链式调用(如promise.then().then())
  2. onFulfilled和onRejected的调用规则:

    • 如果 onFulfilled 不是函数且 Promise 为 Fulfilled,则 promise2 必须以相同的 value 兑现。

    • 如果 onRejected 不是函数且 Promise 为 Rejected,则 promise2 必须以相同的 reason 拒绝。

    • 如果onFulfilledonRejected是函数:

      • 它们必须在 Promise 状态确定后调用(异步执行)。
      • 它们只能被调用一次。
      • onFulfilled 接收 value 作为第一个参数,onRejected 接收 reason 作为第一个参数。
  3. 异步执行:

    • onFulfilledonRejected 必须作为异步任务执行(通常通过微任务队列,如 JavaScript 中的 queueMicrotasksetTimeout)。
    • 这是为了保证一致的异步行为,避免同步调用导致的复杂性。
  4. 返回值处理:

    • onFulfilledonRejected的返回值(称为x)决定promise2的状态:

      • 如果 x 是一个 Promise,则 promise2 等待 x 的状态并跟随其结果(Fulfilled 或 Rejected)。
      • 如果 x 是一个 thenable(具有 then 方法的对象),则尝试调用其 then 方法,并根据结果决定 promise2 的状态。
      • 如果 x 是一个普通值,promise2x 兑现。
      • 如果 onFulfilledonRejected 抛出异常,promise2 以该异常为 reason 拒绝。
  5. 异常处理:

    • 如果 onFulfilledonRejected 抛出异常,promise2 必须以该异常为 reason 拒绝。
    • 如果 promise.then 本身抛出异常,promise2 必须以该异常为 reason 拒绝。
  6. 多重调用:

  • then 方法可以被多次调用,每次调用都会创建一个新的 Promise。
  • 所有注册的 onFulfilledonRejected 回调都会在 Promise 状态确定后按注册顺序执行。

Promise Resolution Procedure(解析过程)

Promise/A+规范定义了一个Promise Resolution Procedure(解析过程),用于处理then方法中onFulfilledonRejected的返回值x,已决定返回的promise2的状态。这个过程是规范的核心,用于确保Promise的互操作性与一致性。

解析过程的步骤

  1. 如果x是一个Promise:

    • promise2等待x的状态:

      • 如果 x 兑现,promise2 以相同的 value 兑现。
      • 如果 x 拒绝,promise2 以相同的 reason 拒绝。
  2. 如果 x 是一个 thenable:

    • 尝试调用x.then,将其作为 Promise 处理:

      • 调用 x.then(onFulfilled, onRejected),并根据结果更新 promise2
      • 如果调用 x.then 抛出异常,promise2 以该异常拒绝。
  3. 如果x是一个合法的JS值: - promise2x 作为 value 兑现。

遵循Promise/A+规范实现Promise

class MyPromise {
  #state = PENDING;
  #value;
  #handlers = [];
  constructor(executor) {
    try {
      executor(this.#resolve, this.#reject);
    } catch (err) {
      this.#reject(err);
    }
  }
​
  #changeState = (state, value) => {
    if (this.#state !== PENDING) {
      return;
    }
    this.#state = state;
    this.#value = value;
    this.#runTasks();
  }
​
  #resolve = (data) => {
    this.#changeState(FULFILLED, data);
  }
​
  #reject = (reason) => {
    this.#changeState(REJECTED, reason);
  }
​
  #runTask({ onFulfilled, onRejected, resolve, reject }) {
    runMicroQueue(() => {
      const executor = this.#state === FULFILLED ? onFulfilled : onRejected;
​
      if (typeof executor !== 'function') {
        this.#state === FULFILLED ? resolve(this.#value) : reject(this.#value);
        return;
      }
​
      try {
        const result = executor(this.#value);
​
        if (isPromise(result)) {
          result.then(resolve, reject);
          return;
        }
​
        resolve(result)
      } catch (err) {
        reject(err);
      }
    });  
  }
​
  #runTasks = () => {
    if (this.#state === PENDING) {
      return;
    }
    while(this.#handlers[0]) {
      this.#runTask(this.#handlers[0]);
      this.#handlers.shift();
    }
  }
​
  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      this.#handlers.push({
        onFulfilled,
        onRejected,
        resolve,
        reject
      });
      this.#runTasks();
    });
  }
​
  catch(onRejected) {
    return this.then(undefined, onRejected);
  }
​
  finally(onSettled) {
    return this.then((data) => {
      onSettled();
      return data;
    }, (reason) => {
      onSettled();
      throw Error(reason);
    });
  }
}