原创 反应热 2025-01-19 09:02 重庆
点击关注公众号,“技术干货” 及时达!
点击关注公众号,“技术干货” 及时达!
前言
今天我们要来实现一个手写的Promise
。如果你对JavaScript的Promise还不是很熟悉,或者想深入了解它的内部机制,那么这篇文章非常适合你,跟着我从零开始,一步步构建Promise吧!
正文
1.初始结构
创建类
let promise=new Promise((resolve,reject)=>{})
我们通常用Promise都是这样new一个实例对吧,那我们就用class创建一个Promise类,如下:
class MyPromise {
constructor(executor) {
const resolve = (value) => {};
const reject = (reason) => {};
executor(resolve, reject);
}
我们可以看到上面的例子,我们在new
一个promise
实例的时候,肯定是需要传入参数的,这个参数是一个函数,而且当我们传入这个函数参数的时候,这个函数参数会被自动执行,所以我们在类的construct
里面添加一个参数exector
,并且在里面执行一下这个参数,因为原生Promsie
里面可以传入传入resolve和reject两个参数,所有我们创建两个函数resolve和reject,并把它传入exector。
创建所需属性和方法
let promise=new Promise((resolve,reject)=>{
resolve('成功')
})
例子中我们知道resolve()
可以改变promsie
状态,promsie
有三个状态,分别是pending、fulfilled、rejected,并且呢只能是pending=>fulfilled
,pending=>rejected
,其它都不可以,所有我们提前把这些状态定义好,我们就用static来创建静态属性,并且在constructor
里面添加一个state
(类里面的this
是指向new
出来的实例对象的)状态为MyPromise.PENDING
(静态属性可以通过类名.属性来访问到)也就是pending状态,这样每个实例创建后就会有自生的属性来判断及变动了,并且我们就可以在自己写的resolve
和reject
函数里面来改变状态了
class MyPromise {
static PENDING = 'pending';
static FULFILLED = 'fulfilled';
static REJECTED = 'rejected';
constructor(executor) {
this.state = MyPromise.PENDING;
this.value = undefined;
this.reason = undefined;
const resolve = (value) => {
if (this.state === MyPromise.PENDING) { // fulfilled状态的上一种状态只能是 pending,状态一经变更就不在逆转
this.state = MyPromise.FULFILLED;
this.value = value; // 保存resolve的参数,留作then中使用
}
};
const reject = (reason) => {
if (this.state === MyPromise.PENDING) { // 同上
this.state = MyPromise.REJECTED;
this.reason = reason; // 保存reject的参数,留作then中使用
}
};
executor(resolve, reject);
}
}
看上面原生的promise
例子可以知道,resolve
和reject
是可以传入参数的,所有分别创建两个value和reason作为它俩的参数,并且我们把参数赋值给实例的valu
和reason
属性。
2.then的实现
我们接着来实现then方法
let promise=new Promise((resolve,reject)=>{
resolve('成功')
reject('失败')
})
promise.then(
value=>console.log(value), // 输出: 成功
reason=>console.log(reason)
)
let promise1=new Promise((resolve,reject)=>{
resolve('成功')
})
promise1.then(
value=>{console.log(value)}, // 输出: 成功
reason=>{console.log(reason)}
)
let promise2=new Promise((resolve,reject)=>{
reject('失败')
})
promise2.then(
value=>{console.log(value)},
reason=>{console.log(reason)} // 输出: 失败
)
<!---->
class MyPromise {
...前面代码一样
then(onFulfilled, onRejected) {
if (this.state === MyPromise.FULFILLED) { // 调用then的Promise对象状态已经变更为 fulfilled
onFulfilled(this.value);
}
if (this.state === MyPromise.REJECTED) {
onRejected(this.reason)
}
}
}
因为then
是在创建实例后再进行调用的,因此我们在constructor
外面创建一个then
方法,看到上面例子中可以发现原生Promise
的then
方法是有两个参数的,且都是回调函数的,then
中的第二个回调充当了catch
一样的效果,在Promise
状态变成更为rejected时触发的,只不过后来加了一个catch
,因此我们给手写的then
里面添加两个参数onFulfilled, onRejected,分别为状态为成功时和拒绝时,并且看到上面例子中只会执行成功状态或失败状态的其中一个,因此我们手写时就要判断状态是什么,再执行相应状态的函数,并且分别为它们传入之前在resolve
或rejrct
中保留的值value
或reason
。
3.解决执行异常
情况一
因为原生的Promise考虑到了很多情况,因此我们要改进我们的Promise
// 原生
let promise = new Promise((resolve, reject) => {
throw new Error('失败test');
})
promise.then(
value => { console.log(value) },
reason => { console.log(reason.message) } // 输出; 失败test
)
可以看到原生的promsie
里面调用then
方法时可以把错误的信息输出出来,再来看看我们写的
// 手写
let promise = new MyPromise((resolve, reject) => {
throw new Error('失败test');
})
promise.then(
value => { console.log(value) },
reason => { console.log(reason.message) }
)
可以看到报错了
class MyPromise {
//...其它代码不变
try {
executor(resolve, reject); // 执行executor函数,传入resolve和reject两个参数
}catch (error) {
reject(error); // 捕获executor中抛出的异常,并执行reject
}
then(){}
}
所以呢我们在执行resolve
和reject
时,进行判断,如果没有报错就正常执行,如果报错就把错误信息传给reject
方法,并且执行reject
方法,这样就不会出现上面的问题.
情况二
// 原生
let promise = new Promise((resolve, reject) => {
resolve('成功')
})
promise.then(
undefined, // 执行是没有问题的
reason => { console.log(reason.message) }
)
可以看到原生的promise
的then
里面的两个参数如果不是函数的话,是被忽略的,执行没有问题,再来看看我们的
// 手写
let promise = new MyPromise((resolve, reject) => {
resolve('成功')
})
promise.then(
undefined,
reason => { console.log(reason.message) }
)
报错了
class MyPromise {
//...前面代码一样
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
if (this.state === MyPromise.FULFILLED) { // 调用then的Promise对象状态已经变更为 fulfilled
onFulfilled(this.value);
}
if (this.state === MyPromise.REJECTED) {
onRejected(this.reason)
}
}
}
所以我们就用三元运算符来判断,如果是函数就把原来的函数赋给它,如果不是函数就把它用函数包着,返回它或把它抛出就可以了。
4.实现异步功能
在对代码进行了一个基本修补后,我们就可以来实现promise
的异步功能了,我们来一个看原生的promise
代码:
// 原生
console.log('1')
let promise = new Promise((resolve, reject) => {
console.log('2')
resolve('成功')
})
promise.then(
value => { console.log(value)},
reason => { console.log(reason)}
)
console.log('3')
// 手写
console.log('1')
let promise = new MyPromise((resolve, reject) => {
console.log('2')
resolve('成功')
})
promise.then(
value => { console.log(value)},
reason => { console.log(reason)}
)
console.log('3')
class MyPromise {
//...前面代码一样
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
if (this.state === MyPromise.FULFILLED) { // 调用then的Promise对象状态已经变更为 fulfilled
setTimeout(() => {
onFulfilled(this.value);
})
}
if (this.state === MyPromise.REJECTED) {
setTimeout(() => {
onRejected(this.reason);
})
}
}
}
原生promise
是异步的并且是微任务,而我们手写的promise
全是同步代码,为了达到这个效果,我们这里方便一点就用setTimeout
来模拟这个异步效果,我们在进行if
状态判别后给代码添加setTimeout
,要不然状态不符合添加异步也是没有意义的。
改完代码的执行结果:
但是异步的问题我们真的解决了吗?我们接着往下看。
5.实现回调保存
这里将要进入难点部分,我们来一个看原生的promise
代码:
console.log('1')
let promise = new Promise((resolve, reject) => {
console.log('2')
setTimeout(() => {
resolve('成功')
console.log('4')
})
})
promise.then(
value => { console.log(value)},
reason => { console.log(reason)}
)
console.log('3')
再来看看我们手写的输出结果
console.log('1')
let promise = new MyPromise((resolve, reject) => {
console.log('2')
setTimeout(() => {
resolve('成功')
console.log('4')
})
})
promise.then(
value => { console.log(value)},
reason => { console.log(reason)}
)
console.log('3')
诶,没有打印resolve
的结果对吧,我们先来捋一捋原生promise
的执行过程
:
console.log('1')--输出1
console.log('2')--输出2
setTimeout()放入宏任务队列
promise.then()放入微任务队列
console.log('3')--输出3
执行微任务,发现resolve()没执行,promise状态没有改变,还是pending状态,那么就不执行
执行宏任务,resolve()把状态变为fulfilled,执行console.log('4')--输出4
最后执行.then--输出成功
再来捋一捋手写的promise
,没有输出成功的原因是当我们执行到then
方法时,我们then
方法是根据条件来执行代码的,也就是说没有符合的情况也就是没有符合的状态,也就是没有情况对应pending
状态对吧,总的来说就是我们的then
方法没有能resolve
执行完状态改变后再执行自己的能力,那改进吧
class MyPromise {
static PENDING = 'pending';
static FULFILLED = 'fulfilled';
static REJECTED = 'rejected';
constructor(executor) {
this.state = MyPromise.PENDING;
this.value = undefined;
this.reason = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.state === MyPromise.PENDING) { // fulfilled状态的上一种状态只能是 pending,状态一经变更就不在逆转
this.state = MyPromise.FULFILLED;
this.value = value; // 保存resolve的参数,留作then中使用
this.onFulfilledCallbacks.forEach(callback => callback(value));
}
};
const reject = (reason) => {
if (this.state === MyPromise.PENDING) { // 同上
this.state = MyPromise.REJECTED;
this.reason = reason; // 保存reject的参数,留作then中使用
this.onRejectedCallbacks.forEach(callback => callback(reason));
}
};
try {
executor(resolve, reject); // 执行executor函数,传入resolve和reject两个参数
}catch (error) {
reject(error); // 捕获executor中抛出的异常,并执行reject
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
if (this.state === MyPromise.FULFILLED) { // 调用then的Promise对象状态已经变更为 fulfilled
setTimeout(() => {
onFulfilled(this.value);
})
}
if (this.state === MyPromise.REJECTED) {
setTimeout(() => {
onRejected(this.reason);
})
}
if(this.state === MyPromise.PENDING){
this.onFulfilledCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected);
}
}
}
我们在then
里面增加一种情况,当then
在resolve
或reject
执行前被执行时也就是pending
状态,我们把then
方法中对应的回调函数放入对应数组中,我们再上面再定义两个数组分别为onFulfilledCallbacks
和onRejectedCallbacks
,为什么是数组呢,因为可能出现多个相同promise.then()
的情况,状态都为pending
,如下面这个代码:
promise.then(value => console.log());
promise.then(value => console.log());
promise.then(value => console.log());
然后我们把它们放入resolve
和reject
中,当resolve
和reject
执行时,去遍历调掉数组里面的回调函数是不是实现了上面我们想要的效果,只能说秒呀,总结就是pending状态时把回调放到resolve
或reject
中去执行
我们来看执行结果:
诶,先输出成功,这是因为resolve
里面都是同步代码所有先执行了resolve()
,所以我们要想办法把resolve
里面异步执行数组里面的函数就可以解决这个问题
class MyPromise {
//... 省略前面的代码
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
if (this.state === MyPromise.FULFILLED) { // 调用then的Promise对象状态已经变更为 fulfilled
setTimeout(() => {
onFulfilled(this.value);
})
}
if (this.state === MyPromise.REJECTED) {
setTimeout(() => {
onRejected(this.reason);
})
}
if(this.state === MyPromise.PENDING){
this.onFulfilledCallbacks.push(value => {
setTimeout(() => {
onFulfilled(value);
})
})
this.onRejectedCallbacks.push(reason=>{
setTimeout(()=>{
onRejected(reason);
})
})
}
}
}
我们通过在把回调函数放入数组时,把它放进一个setTimeout
里面,那是不是数组里面的方法都变成异步了,当resolve
执行数组里面的函数就是异步了,那么不就实现了吗,真正实现了和原生promise
一样的效果,resolve执行完毕后把状态变为fulfilled
或rejected
才执行.then
呀
我们来看效果:
完美
6.实现链式效果
来到我们最后一步,完成.then
的链式功能,也就.then
后面接.then
class MyPromise {
//... 省略前面的代码
then(onFulfilled, onRejected) {
// 判断传入 then 中的参数是否为函数类型,如果是那顺利执行,否则我们人为写入一个函数
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
// then的执行结果要返回一个新的promise
return new MyPromise((resolve, reject) => {
if (this.state === MyPromise.FULFILLED) { // 调用then的Promise对象状态已经变更为 fulfilled
setTimeout(() => {
const result = onFulfilled(this.value); // then自行调用自己的回调函数,接收这次then回调函数里面return出来的值
resolve(result); // 并把下一个then的状态改为fulfilled,把下次then的回调函数里面的参数保存好
});
}
if (this.state === MyPromise.REJECTED) {
setTimeout(() => {
const result = onRejected(this.reason)
reslove(result);
});
}
if (this.state === MyPromise.PENDING) { // 调用then的Promise对象状态没有变更,则缓存then中的回调
this.onFulfilledCallbacks.push(value => {
setTimeout(() => {
const result = onFulfilled(value);
resolve(result);
});
});
this.onRejectedCallbacks.push(reason => {
setTimeout(() => {
const result = onRejected(reason);
reslove(result);
});
});
}
});
}
}
要想后面.then
能接.then
,那then
方法里面得返回一个promise
实例吧,因为原生的then
里面return
出来一个值,会当作下一个then
里面回调函数里面的参数,所以我们const result = onFulfilled(this.value);
把这次的then
执行掉,并接收这次then
里面回调函数里面return
出来的值,再resolve(result);
把下一个then
的状态改为fulfilled
,把下次then
的回调函数里面的参数保存好,reject
是一样的,因为我们每次都是创建一个新的promise对象,每次数组都是不一样的,所有不用担心。
console.log('1')
let promise = new MyPromise((resolve, reject) => {
console.log('2')
setTimeout(() => {
resolve('成功')
console.log('4')
})
})
promise.then(value => {
console.log(value)
return 'hello'
}).then(value=>{
console.log(value)
})
console.log('3')
用我们的promise来执行这段代码
来看最终效果吧:
ok,大功告成呀!这里还有一份更完善的代码,大体思路是不变的,只是加了点try
,catch
,你就可以.then
里面抛出错误了。
class MyPromise {
static PENDING = 'pending';
static FULFILLED = 'fulfilled';
static REJECTED = 'rejected';
constructor(executor) {
this.state = MyPromise.PENDING;
this.value = undefined;
this.reason = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.state === MyPromise.PENDING) { // fulfilled状态的上一种状态只能是 pending,状态一经变更就不在逆转
this.state = MyPromise.FULFILLED;
this.value = value; // 保存resolve的参数,留作then中使用
this.onFulfilledCallbacks.forEach(callback => callback(value)); // then中的回调之在此处已经调用,并接受了参数
}
};
const reject = (reason) => {
if (this.state === MyPromise.PENDING) { // 同上
this.state = MyPromise.REJECTED;
this.reason = reason; // 保存reject的参数,留作then中使用
this.onRejectedCallbacks.forEach(callback => callback(reason));
}
};
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
then(onFulfilled, onRejected) {
// 判断传入 then 中的参数是否为函数类型,如果是那顺利执行,否则我们人为写入一个函数
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
// then的执行结果要返回一个新的promise
return new MyPromise((resolve, reject) => {
if (this.state === MyPromise.FULFILLED) { // 调用then的Promise对象状态已经变更为 fulfilled
setTimeout(() => {
try{
const result = onFulfilled(this.value); // then自行调用自己的回调函数,接收这次then回调函数里面return出来的值
resolve(result); // 并把下一个then的状态改为fulfilled,把下次then的回调函数里面的参数保存好
}catch(error){
reject(error)
}
});
}
if (this.state === MyPromise.REJECTED) {
setTimeout(() => {
try{
const result = onRejected(this.reason)
resolve(result);
}catch(error){
reject(error)
}
});
}
if (this.state === MyPromise.PENDING) { // 调用then的Promise对象状态没有变更,则缓存then中的回调
this.onFulfilledCallbacks.push(value => {
setTimeout(() => {
try{
const result = onFulfilled(value); //if判断是不是promise,这里可能return出一个promise
resolve(result);
}catch(error){
reject(error)
}
});
});
this.onRejectedCallbacks.push(reason => {
setTimeout(() => {
try{
const result = onRejected(reason);
reject(result);
}catch(error){
reject(error)
}
});
});
}
});
}
}
例子:
console.log('1')
let promise = new MyPromise((resolve, reject) => {
console.log('2')
setTimeout(() => {
resolve('成功')
console.log('4')
})
})
promise.then(value => {
console.log(value)
return 'hello'
}).then(value=>{
console.log(value)
throw new Error('失败')
}).then(
value=>console.log(value),
reason=>console.log(reason.message) //输出: 失败
)
console.log('3')
执行结果:
最后提一嘴,还有一种场景就是then
里面返回了一个new Promise
,聪明的你肯定能想到如何解决
总结
本文到这里就结束了,希望对你手写Promise有帮助,如有错误,疏漏的地方恳请指出,感谢你的阅读!
点击关注公众号,“技术干货” 及时达!