原创 反应热 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对象状态已经变更为 fulfilledonFulfilled(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对象状态已经变更为 fulfilledonFulfilled(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对象状态已经变更为 fulfilledsetTimeout(() => {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对象状态已经变更为 fulfilledsetTimeout(() => {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对象状态已经变更为 fulfilledsetTimeout(() => {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的执行结果要返回一个新的promisereturn new MyPromise((resolve, reject) => {if (this.state === MyPromise.FULFILLED) { // 调用then的Promise对象状态已经变更为 fulfilledsetTimeout(() => {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的执行结果要返回一个新的promisereturn new MyPromise((resolve, reject) => {if (this.state === MyPromise.FULFILLED) { // 调用then的Promise对象状态已经变更为 fulfilledsetTimeout(() => {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出一个promiseresolve(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有帮助,如有错误,疏漏的地方恳请指出,感谢你的阅读!
点击关注公众号,“技术干货” 及时达!
