新年快乐!不知不觉,2024已经到了最后一步了,我们即将开启下一段的掘金,自加入这片热土,时常拜读各位的文章,收获良多趣味,也试着敲击自己的旋律,一概不知的小白,也有了一些成长,对前端,对开发的热情愈发旺盛,这也督促着我,持续学习。实践和记录是我觉得很好的学习方法,老师曾说,‘习惯和自律是产生差距的重要原因’,前段时间,自己总给自己找借口,太累了,工作忙等等,属实惭愧,为什么大家可以在深夜学习,而我又为何不能熬个半小时写个demo或是记录文章呢?25年,必须给自己一个Promise了。
来到今天的主题 ES6带来的Promise
那么请问,什么是promise?
在此之前,由于JS是一门单线程的语言,当我们需要发起接口请求等操作时,由于项目的复杂性,不得不使用多重嵌套的回调函数来实现异步编程,然而这种写法可读性并不好而且容易造成回调地狱,那么我们也需要一个方法能够避免这种“俄罗斯套娃”的回调,用另一种方式来切契合这个场景,比如说链式调用,那么,顺应Promise/A+规范的Promise在ES6和大家见面了。
顾名思义,Promise就是承诺,承诺将来会执行。同时,它有三种状态,待定(Pending)、已兑现(fulfilled)、已拒绝(Reject),并且,只能从Pending变化到Resolve或者Rejected,一旦变更,就已经敲定(Settled),不会再变化了。
我们或多或少都了解过Promise,我们来回顾一些使用场景:
// 检查库存
function checkInventory(itemId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const availableItems = [1, 2, 3];
availableItems.includes(itemId) ? resolve(`商品 ${itemId} 有库存`) : reject(`商品 ${itemId} 缺货`);
}, 1000);
});
}
// 处理支付
function processPayment(amount) {
return new Promise((resolve, reject) => {
setTimeout(() => {
Math.random() > 0.2 ? resolve(`支付成功,金额:${amount}`) : reject("支付失败");
}, 1500);
});
}
// 确认订单
function confirmOrder() {
return new Promise((resolve, reject) => {
setTimeout(() => {
Math.random() > 0.1 ? resolve("订单确认成功") : reject("订单确认失败");
}, 1000);
});
}
// 购物流程
function shop(itemId, amount) {
checkInventory(itemId)
.then(inventoryMessage => {
console.log(inventoryMessage);
return processPayment(amount);
})
.then(paymentMessage => {
console.log(paymentMessage);
return confirmOrder();
})
.then(orderMessage => {
console.log(orderMessage);
})
.catch(error => {
console.error("购物流程出错:", error);
});
}
通过以上的例子相信对Promise特性有了一定的了解,那么我们试着去实现一下,加深对Promise的印象。
1. 构造函数 constructor
Promise 的构造函数是我们手写实现的起点,它需要接收一个执行器(executor)函数,这个函数通常包含两个参数:resolve 和 reject,用来分别表示成功和失败的回调。执行器函数会立即执行,所以 Promise 一开始处于 pending(待定)状态。
- 那么,我们需要一个构造函数执行 -> constructor()?
- 能够接收两个参数resolve和reject -> 都是一个回调函数
- 有初始状态 -> 每个promise需要定义一个状态变量,初始值为pending
那么resolve和reject,好像是可以直接调用的,所以在构造函数执行的时候,也会创建这两个方法。 我们先看resolve
- 将状态改为fulfilled
- 接受一个值value
- 并且会执行某一个方法并且将值传递
resolve我们知道了,那么rejected也就照葫芦画瓢了。
2. then方法
其实接受两个参数
onFulfilled:Promise 成功时的回调函数,接收成功的值作为参数。onRejected:Promise 失败时的回调函数,接收失败的原因作为参数。
默认值
在实际使用时,onFulfilled 和 onRejected 并非必须传入,如果没有提供,则会采用默认的处理方式:
onFulfilled默认返回原值,即value => value。onRejected默认抛出错误,即reason => { throw reason },这可以确保异常能够被捕获并传递给链中的后续.catch或.then。
我们再想想,
- 当我们调用
resolve或reject时,参数可以是任何类型的值。这意味着我们需要在then中正确处理这些返回值,尤其是当值本身是另一个Promise时。 - then方法也可以接受一个参数或一个方法,我们需要兼容两种传入。所以说,
then方法会将方法保存起来,当resolve调用时才会触发? 什么时候存呢? - then是可以链式调用的,所以他返回的也是一个promise对象。
所以
-
状态管理:
-
在
then中,我们首先判断当前Promise的状态:fulfilled:直接执行onFulfilled回调。rejected:直接执行onRejected回调。pending:如果当前Promise仍在待定状态,我们将onFulfilled和onRejected保存到相应的回调队列中,待状态变化时再执行。
-
-
异步执行(我们暂时先用
setTimeOut(宏任务)模拟异步微任务):- 使用
setTimeout将回调函数放入异步队列(微任务队列),确保then方法是异步执行的。 - 这样做可以确保
then方法的回调不会立即执行,而是等到当前执行栈清空后再执行,符合 JavaScript 异步行为的预期。
- 使用
-
返回新的
Promise:then方法返回一个新的Promise,以支持链式调用。- 当回调函数返回的是另一个
Promise时,我们需要等待该Promise的完成(即调用它的resolve或reject),并根据其结果决定当前then的返回值。
-
异常处理:
- 如果
onFulfilled或onRejected中的回调函数抛出异常,我们会捕获这个异常并将其传递给下一个链式调用。
- 如果
那么我们可以写出这样一个代码:
class Promise {
constructor(executor) {
this.state = "pending";
this.value = undefined;
this.reason = undefined;
this.onFullfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.state !== "pending") return
this.state = "fulfilled";
this.value = value;
console.log("Resolved with value:", value); // 打印成功信息
this.onFullfilledCallbacks.forEach((callback) => callback(value));
};
const reject = (reason) => {
if (this.state !== "pending") return
this.state = "rejected";
this.reason = reason;
console.log("Rejected with reason:", reason); // 打印错误信息
this.onRejectedCallbacks.forEach((callback) => callback(reason));
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
onFulfilled =
typeof onFulfilled === "function" ? onFulfilled : (value) => value;
onRejected =
typeof onRejected === "function"
? onRejected
: (reason) => {
throw reason;
};
return new Promise((resolve, reject) => {
});
}
}
catch(onRejected) {
return this.then(null, onRejected);
}
}
那么这个返回的Promise中,是不是也会有不同的状态呢? 所以,我们还需要判断当前的promise状态,针对不同情况进行处理,所以刚刚还留了一点
-
处理
Promise的返回值:- 如果
onFulfilled或onRejected的回调函数返回的是一个Promise,我们需要确保新的Promise会等待这个Promise完成,然后继续执行。 - 这就是为什么我们需要使用
instanceof Promise来判断返回值是否是一个Promise。
- 如果
-
状态判断:我们需要判断当前 Promise 的状态是
fulfilled、rejected还是pending。如果是前两者,我们直接调用相应的回调函数。如果是pending,我们需要将回调函数推入一个队列,当状态发生变化时再调用。 -
返回新的 Promise:我们必须确保
then返回一个新的Promise,这是为了链式调用的能力。并且我们需要考虑如何处理返回值(包括其他 Promise)。
return new Promise((resolve, reject) => {
if (this.state === "fulfilled") {
setTimeout(() => {
try {
const result = onFulfilled(this.value);
resolve(result);
} catch (error) {
reject(error);
}
},0);
}
if (this.state === "rejected") {
setTimeout(() => {
try {
const result = onRejected(this.reason);
resolve(result);
}
catch (error) {
reject(error);
}
},0);
}
if (this.state === "pending") {
this.onFullfilledCallbacks.push(() => {
setTimeout(() => {
try {
const result = onFulfilled(this.value);
resolve(result);
} catch (error) {
reject(error);
}
},0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const result = onRejected(this.reason);
resolve(result);
} catch (error) {
reject(error);
}
},0);
});
}
});
那么到此,一个基本的Promise对象就已经搭建好了。
当然还没,还有catch和finally呢?那么我们想一想,这俩到底做了什么?
不难想到catch其实就是then的第二个回调。思考一下。因为他只关心reject的promise对吧。
那么finally呢?
是无论rejected或者resolve都会执行的,那么。也很简单吧。
在finally中,我们一般都会进行一些清理释放工作,比如loading状态的处理、数据的统一格式化等。所以和你想的一样吧。
finally(callback) {
return this.then(
(value) => {
return Promise.resolve(callback()).then(() => value);
},
(reason) => {
return Promise.resolve(callback()).then(() => {
throw reason;
});
}
);
}