Hi! 这里是在县图书馆自习的JustHappy,既然点开了这个JS硬气功,那就一起愉快的修炼吧!我想大家和我一样,在面试中遇到Primise手写题这类的总是想不起来,所以这次我们尝试使用JS硬气功暴打一下,话不多说,我们开始吧。
由于篇幅原因,拆分为三篇供大家享用
你还记得“回调函数”的本真吗?
什么是回调函数?
回调函数是一种编程模式,它允许我们将这个函数作为参数传递给另一个函数,并在某个操作完成时调用这个函数。
就像下面这样......(我们这里使用setTimeout去模拟异步的情况)
function fetchData(callback){
setTimeout(() => {
console.log("数据获取完成");
callback("我*你*");
}, 2000); // 模拟异步操作,2秒后完成
}
function showData(data){
console.log("数据是:",data)
}
fetchData(showData)
是不是很简单,但是为什么我们在项目中很少这么写呢?
详情大家可以看看这篇「JS硬气功👊」从回调函数,迈一步两步走通JS异步
一切回归本源?我们为什么需要Promise?
在传统的JS异步编程中,通常使用回调函数来处理异步操作的结果。如果有多个异步操作需要按顺序执行,就会出现嵌套回调函数的情况,形成所谓的“回调地狱”。这种嵌套结构让代码难以阅读和维护就像下面这样。
只要嵌套的结构一深,我们就连函数的执行顺序都不知道是什么了,那同样的结构使用Promise的链式调用进行构造会是怎么样的呢?
doSomething()
.then(result1 => doSomethingElse(result1))
.then(result2 => doAnotherThing(result2))
.catch(error => console.log('发生错误:', error));
是不是会好很多呢?那一个最基本的Promise用法是什么样子的呢?请看下面
const p1 = new Promise((resolve,reject) => {
resolve(1000)
})
那如果有多个异步并发执行呢?这时候我们就需要使用Promise.all(),就像下面这样
Promise.all([asyncOp1(), asyncOp2(), asyncOp3()])
.then(results => {
console.log('所有操作完成:', results);
})
.catch(error => {
console.log('某个操作失败:', error);
});
那我们只需要知道这些并发异步中哪个异步先执行完,而不是有哪个异步操作失败呢?是吧,就像是这几个异步在race赛跑一样,我们使用Promise.race(),就像下面这样
const promise1 = new Promise((resolve, reject) => setTimeout(resolve, 100, '第一个'));
const promise2 = new Promise((resolve, reject) => setTimeout(resolve, 200, '第二个'));
const promise3 = new Promise((resolve, reject) => setTimeout(resolve, 300, '第三个'));
Promise.race([promise1, promise2, promise3])
.then(result => {
console.log(result); // '第一个'
})
.catch(error => {
console.log(error);
});
你可能也注意到了或者说回忆起了resolve和reject,其实Promise.resolve()
和Promise.reject()
就是返回两种不同状态的Promise对象,resolve对应已解决(fulfilled) 而reject对应 已拒绝(rejected)
Promise.resolve('Hello, World!') .then(result => {
console.log(result); // 'Hello, World!'
});
Promise.reject('Something went wrong')
.then(result => {
console.log(result);
})
.catch(error => {
console.log(error); // 'Something went wrong'
});
我们手写的Promise需要具备什么东西
通过上面的分析我们可以知道,一个相对完整的Promise需要具备以下几种基本功能
- 三种Promise状态反馈:pending、fulfilled、rejected、
- 链式调用:.then() 和 .catch()
- .resolve() 和 .reject() 方法
- .all() 和 .race() 方法
那和我一起敲代码吧!
我们从构造函数开始
为了区分,我们就定义我们自己的Promise叫做MyPromise,回想一下我们是怎么使用Promise的?
const promise = new Promise((resolve, reject) => setTimeout(resolve, 100, 'xxx'));
我们使用new,那么我们的MyPromise就应该是一个Class,里面包含一个构造函数,传入一整个函数作为参数
class MyPromise{
constructor(fn) {
fn(a,b){
......
}
}
}
我们详细处理一下这两个参数哈,也就是一个resolve相关函数和一个reject相关的函数,我们使用resolveHandler和一个rejectHandler,那么就像下面这样
class MyPromise{
constructor(fn) {
const resolveHandler = (value) => {
}
const rejectHandler = (reason) => {
}
fn(resolveHandler,rejectHandler){
}
}
}
我们再想想,一个Promise对象还有什么要素?对的我们需要有一个属性去记录当前Promise的状态、还需要一个属性去记录Promise当前所携带的值(Value),我想你也会像下面这么写
class MyPromise{
state = 'pending' // 最开始的状态是pending
value = undefined // Promise返回成功后携带的值
reason = undefined // Promise返回失败的原因
constructor(fn) {
const resolveHandler = (value) => {
}
const rejectHandler = (reason) => {
}
fn(resolveHandler,rejectHandler){
}
}
}
当然我们MyPromise的用户传入的是整个函数既fn,我们无法保证这个fn是否是正确的,所以我们可以使用try catch去测试一下,就像下面这样
class MyPromise{
state = 'pending' // 最开始的状态是pending
value = undefined // Promise返回成功后携带的值
reason = undefined // Promise返回失败的原因
constructor(fn) {
const resolveHandler = (value) => {
}
const rejectHandler = (reason) => {
}
try{
fn(resolveHandler,rejectHandler)
}catch{
rejectHandler()
}
}
}
那我们的resolveHandler和一个rejectHandler应该怎么写的,很简单,就像下图一样,Promise的状态只能从pending到fulfilled或者从pending到rejected
那么我们就应该这样写
class MyPromise{
state = 'pending' // 最开始的状态是pending
value = undefined // Promise返回成功后携带的值
reason = undefined // Promise返回失败的原因
constructor(fn) {
const resolveHandler = (value) => {
if(this.state === 'pending'){
this.state = 'fulfilled'
this.value = value // 将传入的value存储
}
}
const rejectHandler = (reason) => {
if(this.state === 'pending'){
this.state = 'rejected'
this.reason = reason // 将失败的原因存储
}
}
try{
fn(resolveHandler,rejectHandler)
}catch{
rejectHandler()
}
}
}
到此我们的MyPromise的构造函数算是基本上的结构是有了,如果要完全实现我们的构造函数,我们需要接着先写一下 Promise.then
浅浅看一下Promise.then
我们是如何使用Promise.then的?是不是下面这样,我们传入两个函数,一个成功后执行,一个失败后执行
// 使用 then 方法处理 Promise 的成功和失败
myPromise.then(
(result) => {
// 当 Promise 成功时,这里会被调用
console.log("成功:", result); // 输出:成功: Promise 已成功完成!
},
(error) => {
// 当 Promise 失败时,这里会被调用
console.error("失败:", error);
}
);
由于我们不知道当前的MyPromise的状态,所以我们需要判断当前的状态(😂),所以在我们知道状态之前我们只能将 .then传入的两个回调函数存储起来,只有在确认状态之后再对应执行,于是我们应该在MyPromise类中声明两个数组用于存放这两类回调函数(这边写的比较乱,需要大家理解理解,或者去看看别的教程)
那么目前就是下面这样
class MyPromise{
state = 'pending'
value = undefined
reason = undefined
resolveCallbacks = [] // pending状态下,成功的回调
rejectCallbacks = [] // pending状态下,失败的回调
constructor(fn) {
......
}
// 这边以下是then
then(fn1,fn2){
}
}
什么时候执行resolveCallbacks中的东西呢?什么时候执行rejectCallbacks的呢?很显然是从 panding 变为 fullfilled 的时候是 resolveCallbacks ,从 pending 变为 rejectCallbacks 的时候是 rejectCallbacks,那我们就要在构造函数中操作,也就是修改resolveHandler和rejectHandler
就像下面这样
class MyPromise {
state = "pending";
value = undefined;
reason = undefined;
resolveCallbacks = [];
rejectCallbacks = [];
constructor(fn) {
const resolveHandler = (value) => {
if (this.state === "pending") {
this.state = "fulfilled";
this.value = value;
// 遍历执行成功的回调
this.resolveCallbacks.forEach((fn) => fn(this.value));
}
};
const rejectHandler = (reason) => {
if (this.state === "pending") {
this.state = "rejected";
this.reason = reason;
// 遍历执行失败的回调
this.rejectCallbacks.forEach((fn) => fn(this.reason));
}
};
try {
fn(resolveHandler, rejectHandler);
} catch {
rejectHandler();
}
}
// 这边以下是then
then(fn1, fn2) {}
}
ok,到这里我们的构造函数已经是基本完成了,我们可以试用一下
当resolve时候......
当reject的时候......
ok,这篇就到此为止,我们下篇来实现剩余的方法🚀🚀,像链式调用、 .all() 和 .race() 方法