动手实现一个简单的promise

本文从零开始实现一个简易版Promise,逐步介绍了构造函数、状态控制、then方法的实现及链式调用,最后完善了对返回Promise的支持。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

昨天看了美团技术博客的一篇讲promise的文章,里面实现了一个简单的promise,在这里巩固并修改。

promise的使用是这样的:

        let promise_1 = new myPromise((resolve, reject) => {
            setTimeout(() => { resolve(100); }, 1000);
        });
        promise_1.then(rst => {
            console.log( rst);
        });
复制代码

promise大家平时经常用到,下面我们尝试实现一个简单的promise。

为了方便,假设promise_1是一个promise实例。

构造函数:

        function myPromise(fn) {
            let status = 'pending';//promise对象的状态
            let val;//promise对象的值
        }
复制代码

首先,promise对象在实例化时要求必须传入一个函数。

        function required(name){
            throw new Error('param '+name+' is required')
        }
        function myPromise(fn = required('fn')) {
            let status = 'pending';//promise对象的状态
            let val;//promise对象的值
          	fn();
        }
复制代码

required函数的作用是在用户没传入参数时会报错。

然后,promise对象在实例化时会同步的执行传入的函数(fn)。

promise的回掉函数要通过promise_1.then()来注册,并且promise_1.then()返回的也应该是一个promise,这样才能实现链式调用。

修改一下代码:

    function myPromise(fn = required('fn')) {
        let status = 'pending';//promise对象的状态
        let val;//promise对象的值
        this.then = (onResolve,onReject)=>{
            return new myPromise((resolve, reject)=>{
                //我是promise_return
            });
        }
        fn();
    }
复制代码

现在我们的promise_1有了then,then()也返回一个promise(我们叫他promise_return)。但是这两个promise并没有什么直接关系。

我们现在尝试实现这样的效果:

也就是说我们需要一个数组,来存放promise实例的多个回调。

function myPromise(fn = required('fn')) {
    let next_promiseInfos = [];//当前promise对像的回调们
    let status = 'pending';//promise对象的状态
    let val;//promise对象的值
    this.then = (onResolve,onReject)=>{
        return new myPromise((resolve, reject)=>{
            //我是promise_return
        });
    }
    fn();
}
复制代码

现在我们想想如何将当前promise和他的多个回调相关联。

首先,如果当前promise_1对象状态为pending,此时通过promise_1.then(onResolve)给它添加一个回调,这个onResolve不会立刻执行,而是等待promise_1对象调用resolve并且自身状态变为resolved后,才会执行。我们实现一个handle方法,它会将promise对象的每个回调的相关信息存在数组中。

function myPromise(fn = required('fn')) {
    let next_promiseInfos = [];//当前promise对像的回调们
    let status = 'pending';//promise对象的状态
    let val;//promise对象的值
    this.then = (onResolve,onReject)=>{
        return new myPromise((resolve, reject)=>{
            //我是promise_return
            handle({
                resolve, reject, onResolve, onReject
            });           
        });
    }
    function handle(promise_info) {
         if (status === 'pending') {
             next_promiseInfos.push(promise_info);
             return;
         }
    }
    fn();
}
复制代码

我们可以看到,将onResolve和resolve存入promise_1的next_promiseInfos数组中(关于reject的先不讨论)。

现在我们的promise_1有了各个回调的相关信息(onResolve和每个promise_return的resolve)。我们想一想在promise_1调用resolve时应该发生什么。

promise_1调用resolve时,promise_1的值(代码中的 val)会变成传入resolve函数的值,peomise_1的状态变为resolved,最后,执行promise_1的next_promiseInfos数组中每个元素的onResolve回调,并且为了满足链式调用,需要将回调的执行结果传给下一个promise

我们实现resolve函数:

function resolve(value) {
    val = value;
    status = 'resolved';
    doCallbacks();
}
function doCallbacks() {
    setTimeout(() => {
         next_promiseInfos.forEach(promise_info => {
         handle(promise_info);
         });
    }, 0);
}
复制代码

对handle函数稍作修改:

function handle(promise_info) {
     if (status === 'pending') {
         next_promiseInfos.push(promise_info);
         return;
	}
    rst = promise_info.onResolve(val);//执行回调并返回结果
    promise_info.resolve(rst);//将返回的结果传入下一个promise
}
复制代码

大家对最后一行代码可能不明白,他的作用就是将回调执行的结果传给promise_1.then()生成的新promise,这样就可以实现链式调用。

我们先测试一下现在的代码:

function required(name) {
    throw new Error('param ' + name + ' is required')
}
function myPromise(fn = required('fn')) {
    let next_promiseInfos = [];//当前promise对像的回调们
    let status = 'pending';//promise对象的状态
    let val;//promise对象的值
    this.then = (onResolve, onReject) => {
        return new myPromise((resolve, reject) => {
            //我是promise_return
            handle({
                resolve, reject, onResolve, onReject
            });
        });
    }
    function resolve(value) {
        val = value;
        status = 'resolved';
        doCallbacks();
    }
    function doCallbacks() {
        setTimeout(() => {
            next_promiseInfos.forEach(promise_info => {
                handle(promise_info);
            });
        }, 0);
    }
    function handle(promise_info) {
        if (status === 'pending') {
            next_promiseInfos.push(promise_info);
            return;
        }
        rst = promise_info.onResolve(val);//执行回调并返回结果
        promise_info.resolve(rst);//将返回的结果传入下一个promise
    }
    fn(resolve);
}
复制代码

测试代码:

console.log('程序执行')
let promise_1 = new myPromise((resolve, reject) => {
    setTimeout(() => { resolve(100); }, 1000);
});
promise_1.then(rst => {
    console.log(1, rst);
});
promise_1.then(rst => {
    console.log(2, rst);
});
promise_1.then(rst => {
    console.log(3, rst);
});
promise_1.then(rst => {
    console.log(4, rst);
    return rst +11;
}).then(rst=>{
    console.log('链式调用',rst)
    return rst+2;
}).then(rst=>{
    console.log('链式调用2',rst)
});
复制代码

执行结果:

可以看到,程序按照我们预想的方式执行了。

大家使用promise时可能会在xxx.then()注册的回调中返回一个promise对象,他的执行效果是这样的(注意输出的时间):

在程序执行1秒后,输出1 100,然后该回调返回一个promise,这个promise两秒后resolve(100+2),最后一个回调的实参就是100+2。

我们如何实现这个回调中返回promise的效果呢?

目前,我们实现的promise在resolve后,直接就将状态改为resolve,并且执行后面的回调。这种方式肯定是不行的了。我们在resolve时应该判断传入参数的类型,如果是promise类型,那么我们就等待这个promise执行完成之后再执行resolve。这样才能满足上面说的效果。 我们修改一下resolve的代码:

function resolve(value) {
    //判断一下value的类型
    if (typeof value === 'object' && value.then && typeof value.then === 'function') {
        //如果看起来像一个promise,我们就当他是promise
        value.then(resolve);
        return;
    }
    val = value;
    status = 'resolved';
    doCallbacks();
}
复制代码

如果传入promise_1的resolve函数的value也是一个promise(暂时叫他value_promise),那么我们给他注册一个回调。并且,回调函数就是resolve。

如果value_promise执行完成,他的回调函数会被执行,并将value_promise的值传给回调函数(promise_1的resolve)。也就是说,在value_promise调用他自己的resolve(xxxValue)后,会调用promise_1的resolve(xxxValue)。

这样,promise_1就得到了value_promise的值。

现在的代码:

function required(name) {
    throw new Error('param ' + name + ' is required')
}
function myPromise(fn = required('fn')) {
    let next_promiseInfos = [];//当前promise对像的回调们
    let status = 'pending';//promise对象的状态
    let val;//promise对象的值
    this.then = (onResolve, onReject) => {
        return new myPromise((resolve, reject) => {
            //我是promise_return
            handle({
                resolve, reject, onResolve, onReject
            });
        });
    }
    function resolve(value) {
        //判断一下value的类型
        if (typeof value === 'object' && value.then && typeof value.then === 'function') {
            //如果看起来像一个promise,我们就当他是promise
            value.then(resolve);
            return;
        }
        val = value;
        status = 'resolved';
        doCallbacks();
    }
    function doCallbacks() {
        setTimeout(() => {
            next_promiseInfos.forEach(promise_info => {
                handle(promise_info);
            });
        }, 0);
    }
    function handle(promise_info) {
        if (status === 'pending') {
            next_promiseInfos.push(promise_info);
            return;
        }
        rst = promise_info.onResolve(val);//执行回调并返回结果
        promise_info.resolve(rst);//将返回的结果传入下一个promise
    }
    fn(resolve);
}
复制代码

测试代码:

console.log('程序执行')
let promise_1 = new myPromise((resolve, reject) => {
    setTimeout(() => { resolve(100); }, 1000);
});
promise_1.then(rst => {
    console.log('aaa', rst);
    return new myPromise((resolve)=>{
        setTimeout(()=>{
            resolve(rst+1)
        },1000)
    })
}).then(rst => {
    console.log('bbb', rst);
    return new myPromise((resolve)=>{
        setTimeout(()=>{
            resolve(rst+1)
        },1000)
    })
}).then(rst => {
    console.log('ccc', rst);
    return new myPromise((resolve)=>{
        setTimeout(()=>{
            resolve(rst+1)
        },1000)
    })
}).then(rst=>{
    console.log('finally',rst)
});
复制代码

执行结果:

可以看到程序执行正确,间隔1秒输出结果。

目前,我们已经实现了可以resolve的promise。应该已经理解了实现一个promise的原理。

catch和reject的实现并不复杂,如果有需要会再写一个文章介绍如何实现。

完整的代码:

<!DOCTYPE HTML>
<html lang="en-US">

<head>
    <meta charset="UTF-8">
    <title>promise</title>
</head>

<body>
    <script>
        function required(name){
            throw new Error('param '+name+' is required')
        }
        function myPromise(fn = required('fn')) {
            let next_promiseInfos = [];
            let status = 'pending';
            let val;
            let catcher = null;
            this.then = (onResolve, onReject) => {
                return new myPromise((resolve, reject) => {
                    handle({
                        resolve, reject, onResolve, onReject
                    });
                });
            }
            this.catch = (cb) => {
                catcher = cb;
            }
            function resolve(value) {
                if (typeof value === 'object' && value.then && typeof value.then === 'function') {
                    value.then(resolve,reject);
                    return;
                }
                val = value;
                status = 'resolved';
                doCallbacks();
            }
            function doCallbacks() {
                setTimeout(() => {
                    next_promiseInfos.forEach(promise_info => {
                        handle(promise_info);
                    });
                }, 0);
            }
            function reject(err) {
                status = 'reject';
                val = err;
                if (catcher) {
                    catcher(err);
                    return;
                }
                if (!catcher && next_promiseInfos.length < 1) {
                    throw new Error('uncaught reject:' + err);
                }
                doCallbacks();
            }
            function handle(promise_info) {
                if (status === 'pending') {
                    next_promiseInfos.push(promise_info);
                    return;
                }
                if (status === 'reject') {
                    let cb = promise_info.onReject ? promise_info.onReject : promise_info.reject;
                    cb(val);
                    return;
                }
                let rst;
                try{
                    rst = promise_info.onResolve(val);
                }catch(err){
                    promise_info.reject(err);
                    return;
                }
                promise_info.resolve(rst);
            }
            fn(resolve, reject);
        }
        console.log('程序执行')
        let promise_1 = new myPromise((resolve, reject) => {
            setTimeout(() => { resolve(100); }, 1000);
        });
        promise_1.then(rst => {
            console.log(1,rst);
            return new myPromise((resolve,reject)=>{
                setTimeout(()=>{
                    resolve(rst +2);
                },2000);
            });
        }).then(rst=>{
            console.log('wait',rst);
            throw new Error('出错了');  
        }).then(rst=>{
            console.log(rst);
        }).catch(err=>{
            console.log('catch 到错误了',err)
        });

</script>
</body>

</html>
复制代码

参考:https://tech.meituan.com/promise-insight.html

我的 github

我的 博客

转载于:https://juejin.im/post/5b0658816fb9a07ac85acf55

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值