好的,各位编程界的弄潮儿,欢迎来到老码农的异步世界!今天咱们不聊风花雪月,专攻一门武艺:封装异步操作,打造专属 Promise 类! 🚀
想象一下,你的代码就像一位杂耍艺人,手里同时抛着N个任务。同步代码就像他一次只能抛一个球,必须等一个落地才能抛下一个,效率那个叫一个惨不忍睹!而异步代码,就像他能同时抛N个球,还能优雅地接住每一个,丝滑流畅!
Promise,就是让这位杂耍艺人更加游刃有余的秘诀。它就像一个承诺,承诺将来会给你一个结果,不管成功还是失败,都会给你一个交代,绝不让你苦苦等待,望眼欲穿。
但是!别人家的 Promise 终究是别人家的,用起来总觉得不够贴心。今天,老码农就带大家撸起袖子,打造一个属于自己的 Promise 类,让异步操作从此如臂使指,掌控全局!💪
一、Promise 的前世今生:扒一扒它的底裤
在咱们动手之前,先来了解一下 Promise 到底是个什么玩意儿。别怕,老码农保证不讲晦涩的概念,只用大白话解释:
-
Promise 是一个对象:没错,它就是个对象,一个代表着未来某个不确定结果的对象。你可以把它想象成一个“欠条”,上面写着“将来给你一个值”。
-
它有三种状态:
- Pending (等待中): 就像欠条刚开出来,啥也没发生,未来充满变数。
- Resolved (已解决): 欠条兑现了,你拿到钱了,任务成功完成了!
- Rejected (已拒绝): 欠条作废了,钱没了,任务失败了!
-
状态只能改变一次: 一个 Promise 对象,一旦从 Pending 变成了 Resolved 或 Rejected,就再也回不去了,就像泼出去的水,覆水难收。
-
它自带 then 和 catch 方法:
then()
方法:用来处理 Resolved 状态,也就是成功的情况。catch()
方法:用来处理 Rejected 状态,也就是失败的情况。
二、手撸 Promise:从零开始造轮子
好了,理论知识武装完毕,咱们正式开始造轮子!
- 搭建基本框架
class MyPromise {
constructor(executor) {
// executor 是一个函数,它接受 resolve 和 reject 两个参数
this.state = 'pending'; // 初始状态为 pending
this.value = undefined; // 成功的值
this.reason = undefined; // 失败的原因
this.onResolvedCallbacks = []; // 存储成功的回调
this.onRejectedCallbacks = []; // 存储失败的回调
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'resolved';
this.value = value;
this.onResolvedCallbacks.forEach(fn => fn()); // 执行所有成功的回调
}
};
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn()); // 执行所有失败的回调
}
};
try {
executor(resolve, reject); // 立即执行 executor 函数
} catch (error) {
reject(error); // 如果 executor 函数执行出错,直接 reject
}
}
then(onResolved, onRejected) {
// TODO: 实现 then 方法
}
catch(onRejected) {
// TODO: 实现 catch 方法
}
}
这段代码定义了一个 MyPromise
类,它接受一个 executor
函数作为参数。这个 executor
函数会立即执行,并且接收两个参数:resolve
和 reject
。这两个参数都是函数,用来改变 Promise 的状态。
resolve(value)
:将 Promise 的状态变为 resolved,并且将value
作为成功的值。reject(reason)
:将 Promise 的状态变为 rejected,并且将reason
作为失败的原因。
- 实现 then 方法
then
方法是 Promise 的核心,它用来处理成功和失败的情况。
then(onResolved, onRejected) {
// 处理 onResolved 和 onRejected 不是函数的情况
onResolved = typeof onResolved === 'function' ? onResolved : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
const promise2 = new MyPromise((resolve, reject) => {
if (this.state === 'resolved') {
// 为了拿到 promise2,这里需要用 setTimeout 模拟异步
setTimeout(() => {
try {
const x = onResolved(this.value);
resolvePromise(promise2, x, resolve, reject); // 处理返回值
} catch (e) {
reject(e);
}
}, 0);
}
if (this.state === 'rejected') {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject); // 处理返回值
} catch (e) {
reject(e);
}
}, 0);
}
if (this.state === 'pending') {
// 如果是 pending 状态,先把回调函数存起来
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onResolved(this.value);
resolvePromise(promise2, x, resolve, reject); // 处理返回值
} catch (e) {
reject(e);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject); // 处理返回值
} catch (e) {
reject(e);
}
}, 0);
});
}
});
return promise2; // 返回一个新的 Promise
}
这个 then
方法做了以下几件事:
- 处理参数:如果
onResolved
或onRejected
不是函数,就给它们一个默认值,保证它们总是可调用的。 - 创建新的 Promise:每次调用
then
方法,都会返回一个新的 Promise 对象 (promise2
)。 - 处理不同的状态:
- 如果当前 Promise 的状态是
resolved
,就执行onResolved
函数,并且将结果传递给resolvePromise
函数处理。 - 如果当前 Promise 的状态是
rejected
,就执行onRejected
函数,并且将结果传递给resolvePromise
函数处理。 - 如果当前 Promise 的状态是
pending
,就把onResolved
和onRejected
函数存起来,等到状态改变的时候再执行。
- 如果当前 Promise 的状态是
- 异步执行:为了保证
onResolved
和onRejected
函数是异步执行的,这里使用了setTimeout
。
- 实现 catch 方法
catch
方法是 then
方法的语法糖,它只用来处理失败的情况。
catch(onRejected) {
return this.then(null, onRejected);
}
catch
方法实际上就是调用 then
方法,并且将 onRejected
作为第二个参数传递进去。
- 实现 resolvePromise 函数
resolvePromise
函数是用来处理 then
方法返回值的核心逻辑。它的作用是:
- 判断返回值
x
的类型。 - 如果
x
是一个 Promise 对象,就递归调用resolvePromise
函数,直到x
不是 Promise 对象为止。 - 如果
x
是一个普通值,就将promise2
的状态变为resolved
,并且将x
作为成功的值。 - 如果
x
抛出了一个错误,就将promise2
的状态变为rejected
,并且将错误作为失败的原因。
function resolvePromise(promise2, x, resolve, reject) {
// 防止循环引用
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
}
let called; // 避免多次调用 resolve 或 reject
if (x instanceof MyPromise) {
// 如果 x 是一个 Promise 对象,递归调用 resolvePromise
if (x.state === 'pending') {
// 如果 x 的状态还是 pending,就等待 x 的状态改变
x.then(
y => {
resolvePromise(promise2, y, resolve, reject);
},
reject
);
} else {
x.then(resolve, reject); // 直接调用 x 的 then 方法
}
} else if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
// 如果 x 是一个对象或函数
try {
const then = x.then; // 取出 x 的 then 方法
if (typeof then === 'function') {
// 如果 then 是一个函数,就认为 x 是一个 thenable 对象
then.call(
x,
y => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
},
r => {
if (called) return;
called = true;
reject(r);
}
);
} else {
// 如果 then 不是一个函数,就将 x 作为普通值 resolve
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
// 如果 x 是一个普通值,就将 x 作为普通值 resolve
resolve(x);
}
}
- 添加静态方法
Promise 还有一些静态方法,比如 resolve
、reject
和 all
。
MyPromise.resolve(value)
:创建一个状态为 resolved 的 Promise 对象。
MyPromise.resolve = function(value) {
return new MyPromise((resolve) => {
resolve(value);
});
};
MyPromise.reject(reason)
:创建一个状态为 rejected 的 Promise 对象。
MyPromise.reject = function(reason) {
return new MyPromise((resolve, reject) => {
reject(reason);
});
};
MyPromise.all(promises)
:接收一个 Promise 数组,当所有 Promise 都 resolved 时,返回一个新的 Promise,并且将所有 Promise 的结果放到一个数组中;如果其中一个 Promise rejected,就返回一个新的 Promise,并且将 rejected 的原因作为失败的原因。
MyPromise.all = function(promises) {
return new MyPromise((resolve, reject) => {
const result = [];
let count = 0;
if (promises.length === 0) {
resolve(result);
return;
}
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);
}
},
reject
);
}
});
};
MyPromise.race(promises)
:接收一个 Promise 数组,返回一个新的 Promise,它的状态和第一个改变状态的 Promise 相同。
MyPromise.race = function(promises) {
return new MyPromise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
const promise = promises[i];
MyPromise.resolve(promise).then(
resolve,
reject
);
}
});
};
三、测试你的 Promise:是骡子是马拉出来溜溜
辛辛苦苦撸了这么多代码,是时候检验一下成果了!
const promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('成功了!');
// reject('失败了!');
}, 1000);
});
promise
.then(
value => {
console.log('resolve:', value);
return 'then 的返回值';
},
reason => {
console.log('reject:', reason);
}
)
.then(value => {
console.log('第二个 then:', value);
})
.catch(reason => {
console.log('catch:', reason);
});
运行这段代码,看看控制台的输出是否符合预期。如果一切顺利,恭喜你,你已经成功打造了一个属于自己的 Promise 类!🎉
四、进阶之路:让你的 Promise 更上一层楼
当然,咱们的 Promise 还有很大的提升空间。
- 支持 Promise Chaining: 完善
then
方法的返回值处理,实现链式调用。 - 实现
finally
方法: 无论 Promise 的状态是 resolved 还是 rejected,finally
方法都会执行。 - 添加类型检查: 使用 TypeScript 等工具,为 Promise 添加类型检查,提高代码的健壮性。
- 优化性能: 深入了解 Promise 的内部实现,优化代码,提升性能。
五、总结:掌握 Promise,掌控未来
Promise 是异步编程的利器,掌握它可以让你更好地处理复杂的异步操作。通过手撸 Promise,你可以更深入地了解它的内部机制,并且打造一个更符合自己需求的 Promise 类。
希望今天的分享能帮助你打开异步世界的大门,让你的代码更加优雅、高效!记住,编程的道路永无止境,让我们一起努力,不断学习,不断进步!💪
最后,送给大家一句老码农的座右铭:“代码虐我千百遍,我待代码如初恋!” 😉