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

各位观众,晚上好!我是老码农,今天咱们来聊聊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>"))
}

这是最简单的情况。如果 promisex 指向同一个对象,说明发生了循环引用。 例如:

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 方法: 调用 xthen 方法,并传入两个参数:resolvePromiserejectPromise

    • 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 会调用 thenablethen 方法,并将 resolve 和 reject 函数作为参数传递给它。 thenable 对象在 500 毫秒后调用 resolve 函数,从而 resolve 了 Promise。

重要的细节:called 标志

then 方法的调用过程中,有一个 called 标志,它的作用是确保 resolvePromiserejectPromise 只被调用一次。

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),确保 resolvePromiserejectPromise 只被调用一次 * 如果 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 的状态不可逆。

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

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注