稀土掘金技术社区 01月19日
带你一步一步手写Promise(保姆级讲解,你看你也会写)
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文深入浅出地讲解了如何手写一个Promise,从Promise的基本结构、状态管理到then方法的实现,再到异常处理和异步功能的添加,逐步构建了一个功能完善的Promise。文章详细解释了Promise的pending、fulfilled和rejected三种状态的转换,以及如何利用回调函数处理异步操作。通过手写Promise,读者可以更深入地理解JavaScript中Promise的工作原理和异步编程机制。

🛠️ **初始结构与状态管理**: 通过class创建MyPromise类,定义了pending、fulfilled、rejected三种状态,并使用构造函数初始化状态和保存resolve/reject的参数,确保状态的正确流转。

🔗 **then方法的实现**: 详细解释了then方法的作用,包括如何处理成功和失败的回调函数,并考虑了参数非函数的情况,通过三元运算符进行处理,同时使用setTimeout模拟了异步执行,确保回调函数在合适的时机执行。

🔄 **异步与回调保存**: 针对异步操作,引入了回调队列,在Promise状态变为pending时,将回调函数保存起来,并在状态改变后依次执行,解决了异步情况下then方法无法及时获取结果的问题。

原创 反应热 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里面可以传入传入resolvereject两个参数,所有我们创建两个函数resolvereject,并把它传入exector

创建所需属性和方法

    let promise=new Promise((resolve,reject)=>{        resolve('成功')    })

例子中我们知道resolve()可以改变promsie状态,promsie有三个状态,分别是pendingfulfilledrejected,并且呢只能是pending=>fulfilled,pending=>rejected,其它都不可以,所有我们提前把这些状态定义好,我们就用static来创建静态属性,并且在constructor里面添加一个state(类里面的this是指向new出来的实例对象的)状态为MyPromise.PENDING(静态属性可以通过类名.属性来访问到)也就是pending状态,这样每个实例创建后就会有自生的属性来判断及变动了,并且我们就可以在自己写的resolvereject函数里面来改变状态了

    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例子可以知道,resolvereject是可以传入参数的,所有分别创建两个value和reason作为它俩的参数,并且我们把参数赋值给实例的valureason属性。

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方法,看到上面例子中可以发现原生Promisethen方法是有两个参数的,且都是回调函数的,then中的第二个回调充当了catch一样的效果,在Promise状态变成更为rejected时触发的,只不过后来加了一个catch,因此我们给手写的then里面添加两个参数onFulfilled, onRejected,分别为状态为成功时和拒绝时,并且看到上面例子中只会执行成功状态或失败状态的其中一个,因此我们手写时就要判断状态是什么,再执行相应状态的函数,并且分别为它们传入之前在resolverejrct中保留的值valuereason

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(){}}

所以呢我们在执行resolvereject时,进行判断,如果没有报错就正常执行,如果报错就把错误信息传给reject方法,并且执行reject方法,这样就不会出现上面的问题.

情况二

    // 原生    let promise = new Promise((resolve, reject) => {        resolve('成功')    })
promise.then( undefined, // 执行是没有问题的 reason => { console.log(reason.message) } )

可以看到原生的promisethen里面的两个参数如果不是函数的话,是被忽略的,执行没有问题,再来看看我们的

    // 手写    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执行过程

再来捋一捋手写的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里面增加一种情况,当thenresolvereject执行前被执行时也就是pending状态,我们把then方法中对应的回调函数放入对应数组中,我们再上面再定义两个数组分别为onFulfilledCallbacksonRejectedCallbacks,为什么是数组呢,因为可能出现多个相同promise.then()的情况,状态都为pending,如下面这个代码:

    promise.then(value => console.log());    promise.then(value => console.log());    promise.then(value => console.log());

然后我们把它们放入resolvereject中,当resolvereject执行时,去遍历调掉数组里面的回调函数是不是实现了上面我们想要的效果,只能说秒呀,总结就是pending状态时把回调放到resolvereject中去执行

我们来看执行结果:


诶,先输出成功,这是因为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执行完毕后把状态变为fulfilledrejected才执行.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有帮助,如有错误,疏漏的地方恳请指出,感谢你的阅读!

点击关注公众号,“技术干货” 及时达!

阅读原文

跳转微信打开

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

Promise JavaScript 异步编程 手写Promise
相关文章