各位观众,晚上好!我是老码农,今天咱们来聊聊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 的核心目标是:
- 防止循环引用: 避免 Promise 陷入无限循环。
- 确保 thenable 对象被正确处理: 允许 Promise 与其他实现了
then
方法的对象(也就是 thenable 对象)进行交互。 - 保证 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
属性。 如果获取过程中抛出异常,那么直接用这个异常 rejectpromise
。 -
2.3.3.2
then
是函数吗? 如果then
属性存在,并且是一个函数,那么就认为x
是一个 thenable 对象,需要特殊处理。 -
2.3.3.3 调用
then
方法: 调用x
的then
方法,并传入两个参数:resolvePromise
和rejectPromise
。resolvePromise
:一个函数,用于 resolvepromise
。rejectPromise
:一个函数,用于 rejectpromise
。
then
方法的调用形式如下:then.call(x, resolvePromise, rejectPromise);
x
作为this
上下文传递给then
方法。- 如果
resolvePromise
被调用,那么使用它 resolvepromise
。 - 如果
rejectPromise
被调用,那么使用它 rejectpromise
。
-
2.3.3.4
then
不是函数: 如果then
属性不存在,或者不是一个函数,那么直接用x
的值 fulfillpromise
。
为什么要处理 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 的状态不可逆。
希望今天的讲解对大家有所帮助。 如果你有任何问题,欢迎在评论区提问。 感谢大家的观看,下次再见!