实现 Promise
基础知识
Promise
是 JavaScript 中用于处理异步操作的核心解决方案。它是一个对象,代表了一个尚未完成但最终会完成(或失败)的操作。理解并手动实现一个 Promise,是深入掌握 JS 异步编程的必经之路。
三种状态 (States): 每个 Promise 实例都必须处于以下三种状态之一:
- Pending (进行中): 初始状态,既不是成功,也不是失败。
- Fulfilled (已成功): 意味着操作成功完成。
- Rejected (已失败): 意味着操作失败。
一个 Promise 的状态一旦从
pending
变为fulfilled
或rejected
,就称之为 Settled (已敲定),其状态将永远不会再改变。
执行器函数 (Executor Function): Promise 的构造函数接收一个函数作为参数,我们称之为“执行器”。这个函数会立即执行,并接收两个由 Promise 引擎提供的函数作为参数:
resolve
和reject
。resolve(value)
: 在异步操作成功时调用,将 Promise 的状态从pending
变为fulfilled
,并将成功的值value
传递下去。reject(reason)
: 在异步操作失败时调用,将 Promise 的状态从pending
变为rejected
,并将失败的原因reason
传递下去。
.then()
方法: 这是 Promise 最核心的方法,用于注册在 Promise 状态敲定后需要执行的回调函数。.then(onFulfilled, onRejected)
接收两个可选的函数参数。.then()
方法必须返回一个新的 Promise,这是实现链式调用的关键。
核心思路
我们的目标是创建一个 MyPromise
类,模拟原生 Promise 的行为。其内部结构和逻辑可以分解为以下几个关键部分:
状态机管理: 在
MyPromise
内部,需要一个变量(如this.state
)来管理三种状态。状态的转变必须是单向的(从pending
到fulfilled
或rejected
)。结果存储: 需要变量来存储成功时的值(
value
)和失败时的原因(reason
)。这个值一旦被设置,就不再改变。回调队列: 这是实现异步的关键。因为
.then()
可以在 Promise 状态敲定之前被调用,所以我们需要两个数组(如onFulfilledCallbacks
和onRejectedCallbacks
)来暂存这些回调函数。当 Promise 状态最终改变时(即resolve
或reject
被调用),再依次执行队列中的所有回调。.then()
的实现:.then()
必须返回一个新的 Promise (promise2
)。- 当调用
.then()
时,检查当前 Promise 的状态:- 如果状态是
pending
,则将onFulfilled
和onRejected
回调存入队列。 - 如果状态已经是
fulfilled
或rejected
,则异步地执行相应的回调。
- 如果状态是
- 链式调用的核心:
promise2
的状态由onFulfilled
或onRejected
的执行结果决定。- 如果回调函数执行时抛出异常,
promise2
必须被reject
。 - 如果回调函数返回一个值,
promise2
会用这个值fulfill
。 - (高级) 如果回调函数返回的是另一个 Promise,
promise2
的状态将与这个 Promise 的状态保持一致。
- 如果回调函数执行时抛出异常,
异步执行回调: Promise/A+ 规范要求
.then()
中的回调必须是异步执行的。我们可以使用setTimeout(..., 0)
来模拟这个“微任务”的行为。
代码实现
下面是一个遵循核心思路的 MyPromise
实现。它包含了核心功能,但为简化理解,省略了 Promise/A+ 规范中部分复杂的解析流程。
// 定义三种状态常量
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MyPromise {
constructor(executor) {
// 1. 初始化状态、值和原因
this.state = PENDING;
this.value = undefined;
this.reason = undefined;
// 2. 初始化成功和失败的回调队列
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
// 3. 定义 resolve 函数
const resolve = (value) => {
// 只有在 pending 状态下才能改变
if (this.state === PENDING) {
this.state = FULFILLED;
this.value = value;
// 依次执行所有成功的回调
this.onFulfilledCallbacks.forEach((fn) => fn());
}
};
// 4. 定义 reject 函数
const reject = (reason) => {
// 只有在 pending 状态下才能改变
if (this.state === PENDING) {
this.state = REJECTED;
this.reason = reason;
// 依次执行所有失败的回调
this.onRejectedCallbacks.forEach((fn) => fn());
}
};
// 5. 立即执行 executor,并捕获可能出现的错误
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
// 6. then 方法必须返回一个新的 Promise
const promise2 = new MyPromise((resolve, reject) => {
// 7. 当状态为 fulfilled 时
if (this.state === FULFILLED) {
// 使用 setTimeout 模拟异步
setTimeout(() => {
try {
// onFulfilled 必须是函数
if (typeof onFulfilled === 'function') {
const x = onFulfilled(this.value);
resolve(x); // 将 onFulfilled 的返回值作为 promise2 的成功值
} else {
resolve(this.value); // 如果 onFulfilled 不是函数,则直接透传值
}
} catch (error) {
reject(error);
}
}, 0);
}
// 8. 当状态为 rejected 时
if (this.state === REJECTED) {
setTimeout(() => {
try {
if (typeof onRejected === 'function') {
const x = onRejected(this.reason);
resolve(x); // 注意:.catch 后面可以接 .then,所以这里是 resolve
} else {
reject(this.reason); // 如果 onRejected 不是函数,则直接透传错误
}
} catch (error) {
reject(error);
}
}, 0);
}
// 9. 当状态为 pending 时,将回调函数存入队列
if (this.state === PENDING) {
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
if (typeof onFulfilled === 'function') {
const x = onFulfilled(this.value);
resolve(x);
} else {
resolve(this.value);
}
} catch (error) {
reject(error);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
if (typeof onRejected === 'function') {
const x = onRejected(this.reason);
resolve(x);
} else {
reject(this.reason);
}
} catch (error) {
reject(error);
}
}, 0);
});
}
});
return promise2;
}
}
代码解析
- constructor:初始化
state
为pending
,并准备好存储回调的数组。 - resolve 和 reject 函数:这两个函数被定义在
constructor
内部。它们是改变Promise
状态的唯一途径。核心逻辑是:- 检查当前状态是否为
pending
(保证状态只能改变一次)。 - 更新状态、保存结果(
value
或reason
)。 - 调用
forEach
执行所有已经通过.then()
注册的、等待中的回调函数。
- 检查当前状态是否为
- try...catch:执行器函数
executor
是立即同步执行的。如果它在执行过程中抛出错误,这个错误应该被捕获,并用这个错误来reject
当前的Promise
。 - then 返回新 Promise:
.then
方法的核心是返回一个全新的promise2
。这是所有链式调用的基础。 - then 的状态处理:
.then
内部通过if
判断当前Promise
(this
)的状态:- 已敲定(Fulfilled/Rejected):如果状态已经确定,就不能立即执行回调,而是用
setTimeout(..., 0)
将其放入下一个事件循环中执行,以保证异步性。回调的返回值或抛出的错误,将决定promise2
的命运。 - 进行中(Pending):如果状态还是
pending
,说明resolve
或reject
还没被调用。此时,我们不能执行回调,而是需要将它们包装一下(包装是为了处理返回值和错误),然后push
到对应的回调队列中,等待resolve
或reject
被调用时统一执行。
- 已敲定(Fulfilled/Rejected):如果状态已经确定,就不能立即执行回调,而是用
- 链式调用的实现:在
setTimeout
的回调中,我们执行onFulfilled(this.value)
并将其返回值x
,通过resolve(x)
传递给promise2
。这就实现了值的传递。如果onFulfilled
抛出异常,catch
块会捕获它,并通过reject(error)
将promise2
置为失败状态。这就是链式调用中成功和失败状态的传递机制。