详细解释 Promise A+ 规范中 Promise 的 Resolution Procedure (解析过程) 和如何处理 Thenable 对象。

admin
Sat, 26 Jul 2025 18:29:12 +0000
Javascript, procedure, promise, resolution

各位观众，晚上好！我是老码农，今天咱们来聊聊Promise A+ 规范中一个非常重要的部分——Promise 的 Resolution Procedure，也就是“解析过程”，以及它如何处理神秘的 Thenable 对象。 准备好了吗？系好安全带，我们发车了！ 开胃小菜：Promise A+ 规范是什么？ 在深入解析过程之前，先简单回顾一下 Promise A+ 规范。这玩意儿就像是 Promise 界的“宪法”，定义了 Promise 应该如何工作，保证了不同 JavaScript 库实现的 Promise 行为一致。 想象一下，如果没有这个规范，每个库都按照自己的想法实现 Promise，那我们这些码农岂不是要疯掉？ 正餐：Promise 的 Resolution Procedure (解析过程) 现在进入正题，什么是 Resolution Procedure？ 简单来说，当一个 Promise 对象的状态从 pending (等待) 变成 fulfilled (已完成) 或 rejected (已拒绝) 时，就会触发解析过程。 这个过程决定了 Promise 最终的值（如果 fu …

继续阅读“详细解释 Promise A+ 规范中 Promise 的 Resolution Procedure (解析过程) 和如何处理 Thenable 对象。”

The post 详细解释 Promise A+ 规范中 Promise 的 Resolution Procedure (解析过程) 和如何处理 Thenable 对象。 first appeared on 智猿学院-前后端，数据库，人工智能，云计算等领域前沿技术讲座.

各位观众，晚上好！我是老码农，今天咱们来聊聊Promise A+ 规范中一个非常重要的部分——Promise 的 Resolution Procedure，也就是“解析过程”，以及它如何处理神秘的 Thenable 对象。

准备好了吗？系好安全带，我们发车了！

开胃小菜：Promise A+ 规范是什么？

在深入解析过程之前，先简单回顾一下 Promise A+ 规范。这玩意儿就像是 Promise 界的“宪法”，定义了 Promise 应该如何工作，保证了不同 JavaScript 库实现的 Promise 行为一致。 想象一下，如果没有这个规范，每个库都按照自己的想法实现 Promise，那我们这些码农岂不是要疯掉？

正餐：Promise 的 Resolution Procedure (解析过程)

现在进入正题，什么是 Resolution Procedure？ 简单来说，当一个 Promise 对象的状态从 pending (等待) 变成 fulfilled (已完成) 或 rejected (已拒绝) 时，就会触发解析过程。 这个过程决定了 Promise 最终的值（如果 fulfilled）或者拒绝的原因（如果 rejected）。

Resolution Procedure 的核心目标是：

1. 防止循环引用： 避免 Promise 陷入无限循环。

2. 确保 thenable 对象被正确处理： 允许 Promise 与其他实现了 then 方法的对象（也就是 thenable 对象）进行交互。

3. 保证 Promise 的状态不可逆： 一个 Promise 一旦 settled (fulfilled 或 rejected)，它的状态就不能再改变。

Resolution Procedure 的伪代码流程

为了更清晰地理解这个过程，我们先来看一段伪代码：

resolve(promise, x) {
  if (promise === x) {
    // 2.3.1 If promise and x refer to the same object, reject promise with a TypeError as the reason.
    reject(promise, new TypeError("Chaining cycle detected for promise #<Promise>"))
  }
  else if (x is a promise) {
    // 2.3.2 If x is a promise, adopt its state:
    // 2.3.2.1 If x is pending, promise must remain pending until x is fulfilled or rejected.
    // 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.
    x.then(
      y => resolve(promise, y),
      r => reject(promise, r)
    )
  }
  else if (x is an object or function) {
    // 2.3.3 Otherwise, if x is an object or function,
    try {
      // 2.3.3.1 Let then be x.then.
      then = x.then
    } catch (e) {
      // 2.3.3.2 If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.
      reject(promise, e)
      return
    }

    if (typeof then === 'function') {
      // 2.3.3.3 If then is a function, call it with x as this, first argument resolvePromise, and second argument rejectPromise, where:

      let called = false // To ensure "resolvePromise" or "rejectPromise" is called only once

      try {
        then.call(
          x,
          y => {
            if (called) return
            called = true
            resolve(promise, y)
          },
          r => {
            if (called) return
            called = true
            reject(promise, r)
          }
        )
      } catch (e) {
        // 2.3.3.3.4 If calling then throws an exception e,
        if (called) return // 2.3.3.3.4.1 If resolvePromise or rejectPromise have been called, ignore it.
        called = true
        reject(promise, e) // 2.3.3.3.4.2 Otherwise, reject promise with e as the reason.
      }
    } else {
      // 2.3.3.4 If then is not a function, fulfill promise with x.
      fulfill(promise, x)
    }
  } else {
    // 2.3.4 If x is not an object or function, fulfill promise with x.
    fulfill(promise, x)
  }
}

接下来，我们逐条剖析这段伪代码，看看它到底在干嘛。

2.3.1 同一对象循环引用：

if (promise === x) {
  reject(promise, new TypeError("Chaining cycle detected for promise #<Promise>"))
}

这是最简单的情况。如果 promise 和 x 指向同一个对象，说明发生了循环引用。 例如：

let promise = new Promise((resolve, reject) => {
  resolve();
});

promise.then(value => {
  return promise; // 循环引用！
}).catch(error => {
  console.error(error); // TypeError: Chaining cycle detected for promise #<Promise>
});

在这种情况下，Promise 规范要求用 TypeError 拒绝这个 promise。

2.3.2 x 是一个 Promise：

else if (x is a promise) {
  x.then(
    y => resolve(promise, y),
    r => reject(promise, r)
  )
}

如果 x 本身就是一个 Promise，那么 promise 应该“跟随” x 的状态。 如果 x resolve 了，那么 promise 也 resolve，并且携带相同的值；如果 x reject 了，那么 promise 也 reject，并且携带相同的 reason。

举个例子：

let promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("promise1 resolved");
  }, 100);
});

let promise2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(promise1); // promise2 resolve 的值是 promise1
  }, 200);
});

promise2.then(value => {
  console.log(value); // "promise1 resolved"
});

在这个例子中，promise2 resolve 的值是 promise1。当 promise1 resolve 之后，promise2 最终也会 resolve，并且携带 promise1 resolve 的值 "promise1 resolved"。

2.3.3 x 是一个对象或函数 (Thenable):

这部分是整个解析过程最复杂，也是最有趣的地方。 它处理了 thenable 对象，也就是任何具有 then 方法的对象或函数。

else if (x is an object or function) {
  try {
    then = x.then
  } catch (e) {
    reject(promise, e)
    return
  }

  if (typeof then === 'function') {
    let called = false // To ensure "resolvePromise" or "rejectPromise" is called only once

    try {
      then.call(
        x,
        y => {
          if (called) return
          called = true
          resolve(promise, y)
        },
        r => {
          if (called) return
          called = true
          reject(promise, r)
        }
      )
    } catch (e) {
      if (called) return
      called = true
      reject(promise, e)
    }
  } else {
    fulfill(promise, x)
  }
}

让我们分解一下：

2.3.3.1 获取 then 方法： 首先，尝试从 x 中获取 then 属性。 如果获取过程中抛出异常，那么直接用这个异常 reject promise。

2.3.3.2 then 是函数吗？ 如果 then 属性存在，并且是一个函数，那么就认为 x 是一个 thenable 对象，需要特殊处理。

2.3.3.3 调用 then 方法： 调用 x 的 then 方法，并传入两个参数：resolvePromise 和 rejectPromise。

resolvePromise：一个函数，用于 resolve promise。

rejectPromise：一个函数，用于 reject promise。

then 方法的调用形式如下：

then.call(x, resolvePromise, rejectPromise);

x 作为 this 上下文传递给 then 方法。

如果 resolvePromise 被调用，那么使用它 resolve promise。

如果 rejectPromise 被调用，那么使用它 reject promise。

2.3.3.4 then 不是函数： 如果 then 属性不存在，或者不是一个函数，那么直接用 x 的值 fulfill promise。

为什么要处理 Thenable 对象？

处理 Thenable 对象是为了实现 Promise 的互操作性。 允许 Promise 与其他实现了类似 Promise 接口的对象进行交互，即使这些对象不是真正的 Promise 实例。

例如，早期的 jQuery 的 $.Deferred 对象就不是一个标准的 Promise，但它有一个 then 方法。 通过处理 Thenable 对象，Promise 可以与 $.Deferred 对象进行集成。

Thenable 对象的例子

let thenable = {
  then: function(resolve, reject) {
    setTimeout(() => {
      resolve("Thenable resolved");
    }, 500);
  }
};

let promise = Promise.resolve(thenable);

promise.then(value => {
  console.log(value); // "Thenable resolved"
});

在这个例子中，thenable 对象有一个 then 方法，因此它被认为是一个 Thenable 对象。 当 Promise.resolve(thenable) 被调用时，Promise 会调用 thenable 的 then 方法，并将 resolve 和 reject 函数作为参数传递给它。 thenable 对象在 500 毫秒后调用 resolve 函数，从而 resolve 了 Promise。

重要的细节：called 标志

在 then 方法的调用过程中，有一个 called 标志，它的作用是确保 resolvePromise 和 rejectPromise 只被调用一次。

let called = false;

try {
  then.call(
    x,
    y => {
      if (called) return;
      called = true;
      resolve(promise, y);
    },
    r => {
      if (called) return;
      called = true;
      reject(promise, r);
    }
  );
} catch (e) {
  if (called) return;
  called = true;
  reject(promise, e);
}

这是为了防止某些不规范的 Thenable 对象多次调用 resolve 或 reject 函数，导致 Promise 的状态发生混乱。

2.3.4 x 不是对象或函数：

else {
  fulfill(promise, x)
}

如果 x 既不是 Promise，也不是对象或函数，那么直接用 x 的值 fulfill promise。 这就是最简单的情况，例如：

let promise = Promise.resolve(123);

promise.then(value => {
  console.log(value); // 123
});

总结：Resolution Procedure 的核心要点

为了更好地理解 Promise 的 Resolution Procedure，我们用表格总结一下：

条件 | 行为
promise === x | 用 TypeError 拒绝 promise (防止循环引用)
x 是一个 Promise | promise "跟随" x 的状态（如果 x resolve，则 promise 也 resolve；如果 x reject，则 promise 也 reject）
x 是一个对象或函数 (Thenable) | 尝试获取 x.then： 如果获取过程中抛出异常，用这个异常 reject promise 如果 x.then 是一个函数：调用 x.then(resolvePromise, rejectPromise)，确保 resolvePromise 和 rejectPromise 只被调用一次 * 如果 x.then 不是一个函数：用 x 的值 fulfill promise
其他情况 | 用 x 的值 fulfill promise

代码示例：模拟 Promise 的 Resolution Procedure

为了更好地理解 Resolution Procedure，我们可以尝试自己实现一个简单的 Promise，并模拟这个过程。

class MyPromise {
  constructor(executor) {
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];

    let resolve = (value) => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onResolvedCallbacks.forEach(fn => fn());
      }
    };

    let reject = (reason) => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn => fn());
      }
    };

    try {
      executor(resolve, reject);
    } catch (e) {
      reject(e);
    }
  }

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

    let promise2 = new MyPromise((resolve, reject) => {
      if (this.state === 'fulfilled') {
        setTimeout(() => { // 模拟异步
          try {
            let x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      }

      if (this.state === 'rejected') {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      }

      if (this.state === 'pending') {
        this.onResolvedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });

        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
      }
    });

    return promise2;
  }
}

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

  let called;

  if ((typeof x === 'object' && x != null) || typeof x === 'function') {
    try {
      let then = x.then;
      if (typeof then === 'function') {
        then.call(
          x,
          (y) => {
            if (called) return;
            called = true;
            resolvePromise(promise2, y, resolve, reject);
          },
          (r) => {
            if (called) return;
            called = true;
            reject(r);
          }
        );
      } else {
        resolve(x);
      }
    } catch (e) {
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
    resolve(x);
  }
}

// 测试
let promise = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(100);
  }, 1000);
});

promise.then(value => {
  console.log('成功：', value);
}, reason => {
  console.log('失败：', reason);
});

这个示例代码实现了一个简单的 MyPromise 类，以及 resolvePromise 函数，用于模拟 Promise 的 Resolution Procedure。 虽然这个实现非常简化，但它涵盖了 Resolution Procedure 的核心逻辑，可以帮助你更好地理解这个过程。

总结

今天我们深入探讨了 Promise A+ 规范中的 Resolution Procedure，以及它如何处理 Thenable 对象。 理解这个过程对于编写高质量的 Promise 代码至关重要。 记住，Resolution Procedure 的核心目标是防止循环引用，正确处理 Thenable 对象，并保证 Promise 的状态不可逆。

希望今天的讲解对大家有所帮助。 如果你有任何问题，欢迎在评论区提问。 感谢大家的观看，下次再见！