笔记创作活动的第 10 天。
前言
相信大家对Promise一定不陌生,在日常开发中会经常遇到它,如果对Promise还不是很了解的小伙伴可以看一下 详解ES6之Promise对象 这篇文章,接下来让我们从零入手一步步实现一个简版的Promise吧!
首先,我们先来看一下平常是如何使用Promise的:
let p = new Promise((resolve, reject) => {
if(成功){
resolve(数据);
} else{
reject(原因);
}
})
p.then(res => {
console.log(res);
}).catch(err => {
console.log(err);
})
需求:
- 实现
resolve和reject - 状态一旦确定则不可变
- 实现
then方法 - 解决定时器、请求等异步改变状态问题
- 实现
then链式调用 - 模拟
then的微任务特性 - 实现
allraceany等静态方法
一、resolve和reject
实现resolve和reject
class MyPromise {
constructor(callback) {
// 初始化
this.init();
// 执行传进来的回调函数
callback(this.resolve, this.reject);
}
init() {
this.PromiseResult = null; // 结果值
this.PromiseState = "pending"; // 状态
// 绑定上下文
this.resolve = this.resolve.bind(this);
this.reject = this.reject.bind(this);
}
resolve(value) {
// 修改状态
this.PromiseState = "fulfilled";
// 给 PromiseResult 赋值
this.PromiseResult = value;
}
reject(reason) {
this.PromiseState = "rejected";
this.PromiseResult = reason;
}
}
在初始化时有个很重要的点,就是给resolve和reject函数绑定this上下文,目的是为了使它俩的this指向永远指向当前MyPromise的实例对象,避免随着函数执行环境的改变而改变。
使用try-catch捕获错误
constructor(callback) {
// 初始化
this.init();
try {
// 执行传进来的回调函数
callback(this.resolve, this.reject);
} catch (e) {
// 捕捉到错误直接执行reject
this.reject(e);
}
}
我们来测试一下是否可用:
const mp = new MyPromise((resolve, reject) => {
resolve("成功");
reject("失败");
});
console.log(mp); // MyPromise { PromiseState: "rejected", PromiseResult: "失败" }
上面这段代码乍一看没啥问题,仔细思考一下!Promise的状态一旦被确定就不可以改变,上面已经resolve了却还是被reject改变了结果。接下来我们解决一下这个问题。
状态不可变
我们只需要短短的一行代码就能解决!
resolve(value) {
// 判断 PromiseState 是否被修改,若是则直接 return
if (this.PromiseState !== "pending") return; // 新增
this.PromiseState = "fulfilled";
this.PromiseResult = value;
}
reject(reason){
// 判断 PromiseState 是否被修改,若是则直接 return
if (this.PromiseState !== "pending") return; // 新增
this.PromiseState = "rejected";
this.PromiseResult = reason;
}
我们再来看看效果:
const mp = new MyPromise((resolve, reject) => {
resolve("成功");
reject("失败");
});
console.log(mp); // MyPromise { PromiseState: "fulfilled", PromiseResult: "成功" }
二、then方法
我们先来看看Promise的then怎么使用:
const p1 = new Promise((resolve, reject) => {
resolve('成功')
})
p1.then(
res => console.log(res), // 立即输出'成功'
err => console.log(err)
)
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('失败')
}, 3000)
})
p2.then(
res => console.log(res),
err => console.log(err) // 3秒后输出'失败'
)
const p3 = new Promise((resolve, reject) => {
resolve(5)
})
p3.then(res => 2 * res)
.then(res => console.log(res)) // 链式调用,输出 10
重点:
then可以接受两个回调函数,分别是成功时执行的回调(对应fulfilled状态)和失败时执行的回调(对应fulfilled状态)。- 若
resolve或reject在定时器等异步回调里,则定时器结束后再执行then。 then支持链式调用,后面的then的执行受上一次then返回值的影响。
实现then方法
then(onFulfilled, onRejected) {
// 接收两个回调函数 onFulfilled, onRejected
// 参数校验,确保一定是函数
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (val) => val;
onRejected = typeof onRejected === "function" ? onRejected : (reason) => { throw reason };
if (this.PromiseState === "fulfilled") {
// 如果当前为成功状态,执行第一个回调
onFulfilled(this.PromiseResult);
} else if (this.PromiseState === "rejected") {
// 如果当前为失败状态,执行第二个回调
onRejected(this.PromiseResult);
}
}
实现定时器等异步情况的处理
普通情况下的then我们已经实现了,但在定时器等异步状况下的,我们还有待处理。看个例子:
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 3000)
}).then(
res => console.log(res)
)
我们期望达到的效果是,3秒后控制台输出 success。但是结合我们写的then函数来看,我们没有办法控制then函数的延迟执行,但是then函数执行它的两个回调主要是依靠判断PromiseState状态,那么我们就可以控制then函数执行它两个回调的时机(即暂时将then函数的参数保存起来,等定时器结束,PromiseState状态改变,再去调用回调函数)。
具体我们来看代码实现。
init(){
this.PromiseResult = null; // 结果值
this.PromiseState = "pending"; // 状态
// 绑定上下文
this.resolve = this.resolve.bind(this);
this.reject = this.reject.bind(this);
// 新增
this.onFulfilledCallbacks = []; // 保存成功回调
this.onRejectedCallbacks = []; // 保存失败回调
}
resolve(value) {
if (this.PromiseState !== "pending") return;
this.PromiseState = "fulfilled";
this.PromiseResult = value;
// 新增 - 执行保存的成功回调
while (this.onFulfilledCallbacks.length) {
this.onFulfilledCallbacks.shift()(this.PromiseResult);
}
}
reject(reason) {
if (this.PromiseState !== "pending") return;
this.PromiseState = "rejected";
this.PromiseResult = reason;
// 新增 - 执行保存的失败回调
while (this.onRejectedCallbacks.length) {
this.onRejectedCallbacks.shift()(this.PromiseResult);
}
}
then(onFulfilled, onRejected) {
// ...省略
// 新增
if (this.PromiseState === "fulfilled") {
// 如果当前为成功状态,执行第一个回调
onFulfilled(this.PromiseResult);
} else if (this.PromiseState === "rejected") {
// 如果当前为失败状态,执行第二个回调
onRejected(this.PromiseResult);
} else if (this.PromiseState === "pending") {
// 如果状态为待定状态,暂时保存两个回调
this.onFulfilledCallbacks.push(onFulfilled.bind(this));
this.onRejectedCallbacks.push(onRejected.bind(this));
}
}
整理一下思路:
- 为什么使用数组保存
then的回调?因为一个Promise实例可以调用多次then函数,所以有可能有多个回调。 - 使用了定时器等异步操作的
Promise实例的then的回调不在then函数里面执行?因为PromiseState状态没有立即改变,我们需要通过状态来判断执行then的哪个回调,而状态的改变正是在resolve或reject里面进行修改的。
我们来试试是否可用:
const mp = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 3000)
}).then(
res => console.log(res) // 3秒后输出success
)
实现链式调用
重点:
- 下一次
then的执行,受上一次then的执行情况的影响,实参是上一次then的返回值。 then方法本身会返回一个新的Promise对象。- 如果
then的回调的返回值是一个Promise对象,则then返回的Promise会取决于回调的Promise的resolve或reject。 - 如果
then的回调的返回值不是一个Promise对象,则then返回的Promise的状态是fulfilled,值是那个返回值。(即Promise.resolve(value))
看个例子(搞懂这个例子就能明白上面四点):
let p = new Promise((resolve, reject) => {
reject(2)
}) // p 是一个Promise的实例对象,它的状态是 rejected ,它的值是 2
// 执行 p.then
let pt1 = p.then(
res => { console.log(res) }, // 这里不会执行,因为p的状态是rejected
err => {
// then的失败回调,err 参数是 p 的值
// 回调函数返回一个Promise实例,这个实例又reject了,值是 2 * 2 = 4
return new Promise((resolve, reject) => {
reject(2 * err)
})
}
) // pt1 是 p.then 返回的新的Promise实例对象,它的状态是 rejected ,它的值是 4
console.log(pt1)
// 因为pt1也是Promise实例,所以它也可以调用then,执行 pt1.then
let pt2 = pt1.then(
res => { console.log(res) }, // 这里不会执行,因为pt1的状态是rejected
err => {
// 回调函数返回一个number类型的数值
// 即便它是在失败回调里返回的,但是由于返回的是非Promise对象,所以pt1.then的返回值相当于Promise.resolve(2 * err)
return 2 * err
}
) // pt2 是 pt1.then 返回的新的Promise实例对象,它的状态是 fulfilled ,它的值是 8
console.log(pt2)
复制代码
理解了链式调用的基本规则后,我们来实现一下:
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
let thenPromise = new MyPromise((resolve, reject) => {
const handleCallback = cb => {
try {
// 执行then的回调函数
const res = cb(this.PromiseResult) // 拿到then的回调函数的返回值
if (res instanceof MyPromise) {
// 如果返回值是MyPromise实例对象,则我们也需要执行这个实例对象的then
res.then(resolve, reject)
} else {
// 如果返回值不是MyPromise实例对象,就直接resolve
resolve(res)
}
} catch (err) {
// 出现错误则直接reject,并再往控制台抛出一个错误
reject(err)
throw new Error(err)
}
}
if (this.PromiseState === 'fulfilled') {
handleCallback(onFulfilled)
} else if (this.PromiseState === 'rejected') {
handleCallback(onRejected)
} else if (this.PromiseState === 'pending') {
this.onFulfilledCallbacks.push(handleCallback.bind(this, onFulfilled))
this.onRejectedCallbacks.push(handleCallback.bind(this, onRejected))
}
})
// 返回这个新的MyPromise实例对象
return thenPromise
}
浅尝一下:
const mp = new MyPromise((resolve, reject) => {
resolve(2);
}).then((res) => res * 2)
.then((res) => console.log("成功", res)) // 输出 '成功 4'
复制代码
现在还有一个比较细节的点需要完善,就是Promise.then是微任务,我们需要在代码中使用queueMicrotask模拟微任务的执行。
继续完善代码:
then(onFulfilled, onRejected){
...省略
let thenPromise = new MyPromise((resolve, reject) => {
const handleCallback = (cb) => {
// 新增
queueMicrotask(()=>{
try {
const res = cb(this.PromiseResult)
if (res instanceof MyPromise) {
res.then(resolve, reject)
} else {
resolve(res)
}
} catch (err) {
reject(err)
throw new Error(err)
}
})
}
...省略
}
}
来个测试代码测试一下:
setTimeout(() => {
console.log(3);
});
const p = new MyPromise((resolve, reject) => {
resolve(2);
}).then((res) => console.log(res));
console.log(1);
// 预期结果 1 2 3
写在最后
完整代码在Script里面。
如果文中有任何问题,还请各位大佬们指出,欢迎大家一起探讨~