自定义 Promise 实现:深入解析 then、catch 和 finally
大家好!今天我们来一起深入探讨如何实现一个自定义的 Promise,并深入解析其 then、catch 和 finally 的执行逻辑。Promise 作为现代 JavaScript 中处理异步操作的重要工具,理解其内部机制对于编写高效、可维护的代码至关重要。
Promise 的基本概念
在开始实现之前,我们先回顾一下 Promise 的几个关键概念:
- 状态 (State):
Promise具有三种状态:- Pending (待定): 初始状态,既没有被兑现,也没有被拒绝。
- Fulfilled (已兑现): 操作成功完成。
- Rejected (已拒绝): 操作失败。
- 值 (Value):
Promise对象保存着一个值,该值在Promise状态变为Fulfilled时可用。 - 原因 (Reason):
Promise对象也可能保存一个原因,该原因在Promise状态变为Rejected时可用。 - 不可变性 (Immutability): 一旦
Promise的状态变为Fulfilled或Rejected,它就不能再改变。 then方法: 用于注册在Promise状态变为Fulfilled或Rejected时要执行的回调函数。catch方法: 用于注册在Promise状态变为Rejected时要执行的回调函数,是then(null, rejection)的语法糖。finally方法: 用于注册在Promise状态变为Fulfilled或Rejected时都要执行的回调函数,无论结果如何。
实现 Promise 骨架
首先,我们创建一个 MyPromise 类,并定义其构造函数和状态。
class MyPromise {
constructor(executor) {
this.state = 'pending'; // 初始状态为 pending
this.value = undefined; // 成功的值
this.reason = undefined; // 失败的原因
this.onFulfilledCallbacks = []; // 存储成功的回调
this.onRejectedCallbacks = []; // 存储失败的回调
try {
executor(this.resolve.bind(this), this.reject.bind(this));
} catch (error) {
this.reject(error);
}
}
resolve(value) {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach(fn => fn()); // 执行所有成功的回调
}
}
reject(reason) {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn()); // 执行所有失败的回调
}
}
then(onFulfilled, onRejected) {
// ... 实现 then 方法
}
catch(onRejected) {
return this.then(null, onRejected); // catch 是 then 的语法糖
}
finally(callback) {
// ... 实现 finally 方法
}
}
在构造函数中,我们接收一个 executor 函数,该函数接收 resolve 和 reject 两个函数作为参数。executor 函数负责执行异步操作,并在操作成功时调用 resolve,操作失败时调用 reject。
我们还定义了 onFulfilledCallbacks 和 onRejectedCallbacks 数组,用于存储 then 方法注册的回调函数。当 Promise 的状态变为 fulfilled 或 rejected 时,我们会依次执行这些回调函数。
实现 then 方法
then 方法是 Promise 的核心方法,它用于注册在 Promise 状态变为 fulfilled 或 rejected 时要执行的回调函数。then 方法返回一个新的 Promise 对象,允许链式调用。
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
const promise2 = new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
}
if (this.state === 'rejected') {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
}
if (this.state === 'pending') {
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});
}
});
return promise2;
}
这个 then 方法的实现包含以下几个关键步骤:
- 参数处理: 首先,我们对
onFulfilled和onRejected进行类型检查。如果它们不是函数,则提供默认的函数:onFulfilled默认函数返回value,实现值的穿透。onRejected默认函数抛出reason,实现错误穿透。
- 创建新的 Promise: 创建一个新的
MyPromise对象promise2,并返回它。这是实现链式调用的关键。 - 处理不同的状态: 根据当前
Promise的状态,执行不同的逻辑:- fulfilled: 异步执行
onFulfilled,并将结果x传递给resolvePromise函数。 - rejected: 异步执行
onRejected,并将结果x传递给resolvePromise函数。 - pending: 将
onFulfilled和onRejected函数分别添加到onFulfilledCallbacks和onRejectedCallbacks数组中,等待Promise状态改变时执行。 使用setTimeout是为了模拟异步,保证promise2创建完成
- fulfilled: 异步执行
- 异步执行: 使用
setTimeout模拟异步执行,确保在当前调用栈清空后执行回调函数。 resolvePromise函数: 这个函数用于处理onFulfilled或onRejected回调函数的返回值x,并根据x的类型来决定promise2的状态。 这个函数非常重要,处理了then返回的值x的情况,让then可以链式调用。
实现 resolvePromise 函数
resolvePromise 函数用于处理 onFulfilled 或 onRejected 回调函数的返回值 x,并根据 x 的类型来决定 promise2 的状态。这是 Promise 实现中最复杂的部分。
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<MyPromise>'));
}
let called = false; // 防止多次调用 resolve 或 reject
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
try {
const then = x.then; // 取出 x 的 then 方法
if (typeof then === 'function') {
then.call(
x,
y => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject); // 递归调用 resolvePromise
},
r => {
if (called) return;
called = true;
reject(r);
}
);
} else {
if (called) return;
called = true;
resolve(x);
}
} catch (error) {
if (called) return;
called = true;
reject(error);
}
} else {
if (called) return;
called = true;
resolve(x);
}
}
resolvePromise 函数的逻辑如下:
- 循环引用检测: 首先,检查
promise2是否等于x。如果相等,说明发生了循环引用,抛出一个TypeError。 - 判断
x的类型: 判断x是否为对象或函数。- 如果
x是对象或函数:- 尝试取出
x的then方法。 - 如果
then是一个函数,则认为x是一个Promise对象。调用then方法,并将resolve和reject作为参数传递给它。 - 如果
then不是一个函数,则将x作为值传递给resolve函数,使promise2变为fulfilled状态。
- 尝试取出
- 如果
x不是对象或函数: 将x作为值传递给resolve函数,使promise2变为fulfilled状态。
- 如果
- 防止多次调用: 使用
called变量来防止多次调用resolve或reject函数。 - 错误处理: 使用
try...catch块来捕获可能发生的错误,并将错误传递给reject函数,使promise2变为rejected状态。
实现 finally 方法
finally 方法用于注册在 Promise 状态变为 fulfilled 或 rejected 时都要执行的回调函数,无论结果如何。finally 方法返回一个新的 Promise 对象,并将原始 Promise 的值或原因传递给它。
finally(callback) {
return this.then(
value => {
return MyPromise.resolve(callback()).then(() => value);
},
reason => {
return MyPromise.resolve(callback()).then(() => { throw reason; });
}
);
}
finally 方法的实现原理如下:
- 使用
then方法:finally方法实际上是基于then方法实现的。 - 无论成功或失败都执行回调: 在
then方法的onFulfilled和onRejected回调函数中,都调用callback函数。 - 返回原始值或原因: 在
callback函数执行完毕后,返回原始Promise的值或原因,以保持Promise链的完整性。- 如果原始
Promise状态为fulfilled,则返回value。 - 如果原始
Promise状态为rejected,则抛出reason。
- 如果原始
- 确保回调异步执行完成: 使用
MyPromise.resolve(callback())确保callback函数异步执行完成,避免阻塞Promise链。
完善 Promise 实现:静态方法
为了使我们的 MyPromise 更完整,我们还需要实现一些静态方法,例如 resolve、reject 和 all。
MyPromise.resolve(value):
static resolve(value) {
return new MyPromise((resolve, reject) => {
resolve(value);
});
}
MyPromise.reject(reason):
static reject(reason) {
return new MyPromise((resolve, reject) => {
reject(reason);
});
}
MyPromise.all(promises):
static all(promises) {
return new MyPromise((resolve, reject) => {
if (!Array.isArray(promises)) {
return reject(new TypeError('Argument must be an array'));
}
const result = [];
let count = 0;
if (promises.length === 0) {
return resolve(result);
}
for (let i = 0; i < promises.length; i++) {
const promise = promises[i];
MyPromise.resolve(promise).then(
value => {
result[i] = value;
count++;
if (count === promises.length) {
resolve(result);
}
},
reason => {
reject(reason);
}
);
}
});
}
MyPromise.all(promises) 方法的逻辑如下:
- 参数校验: 首先,检查
promises是否为数组。如果不是,则抛出一个TypeError。 - 初始化: 创建一个空数组
result用于存储结果,并初始化计数器count为 0。 - 空数组处理: 如果
promises数组为空,则直接返回一个fulfilled状态的Promise,值为result。 - 遍历
promises数组: 遍历promises数组,并将每个元素转换为Promise对象。 - 处理每个
Promise对象: 对于每个Promise对象,使用then方法注册回调函数:- 成功回调: 将
Promise的值添加到result数组中,并将计数器count加 1。如果count等于promises数组的长度,则说明所有Promise对象都已成功,将result作为值传递给resolve函数,使Promise变为fulfilled状态。 - 失败回调: 将
reason作为原因传递给reject函数,使Promise变为rejected状态。
- 成功回调: 将
测试我们的 Promise 实现
现在,我们可以使用一些简单的测试用例来验证我们的 MyPromise 实现是否正确。
const promise1 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('Promise 1 resolved');
}, 1000);
});
promise1
.then(value => {
console.log('then 1:', value);
return 'then 1 return';
})
.then(value => {
console.log('then 2:', value);
throw new Error('Error in then 2');
})
.catch(error => {
console.error('catch:', error);
})
.finally(() => {
console.log('finally');
});
MyPromise.resolve('Resolved value')
.then(value => console.log('Resolved promise:', value));
MyPromise.reject('Rejected reason')
.catch(reason => console.error('Rejected promise:', reason));
MyPromise.all([
MyPromise.resolve(1),
new MyPromise(resolve => setTimeout(() => resolve(2), 500)),
3,
])
.then(values => console.log('All resolved:', values))
.catch(reason => console.error('All rejected:', reason));
总结重点
我们深入探讨了如何实现一个自定义的 Promise,并详细解析了 then、catch 和 finally 的执行逻辑。重点在于对 Promise 状态的维护、回调函数的管理、以及 resolvePromise 函数的处理,它决定了链式调用中 Promise 的状态传递和结果处理。
进一步的思考方向
- 异步任务调度: 研究如何优化异步任务的调度,例如使用微任务队列(microtask queue)来提高性能。
- Promise A+ 规范: 深入理解 Promise A+ 规范,确保你的 Promise 实现符合标准。
- 错误处理: 探索更高级的错误处理机制,例如使用
unhandledRejection事件来捕获未处理的 Promise 拒绝。
希望今天的分享对大家有所帮助,谢谢!