Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

性能优化:基于Promise的状态共享 #77

Open
wuyunqiang opened this issue Sep 20, 2024 · 0 comments
Open

性能优化:基于Promise的状态共享 #77

wuyunqiang opened this issue Sep 20, 2024 · 0 comments

Comments

@wuyunqiang
Copy link
Owner

一图胜千文:

截屏2024-07-30 下午3.10.06.png

背景:

在实际的软件开发过程中,异步操作是非常常见的场景。这些异步操作涵盖了各种各样的情况,其中最常见的就是网络请求和客户端与服务器之间的交互。这些操作往往需要消耗一定的时间和系统资源,比如CPU计算时间、内存占用甚至网络带宽。如果在短时间内相继进行多次异步操作,特别是这些操作是重复的,那么就会形成不必要的资源浪费。此时,我们就需要对这些操作进行优化,以减少资源的浪费,并提高应用程序的性能

在JavaScript中,Promise是管理异步操作的重要工具。Promise不仅可以让异步操作的编写更加简洁,而且还支持链式调用,使得代码结构更加清晰。然而,当面临上述的短时间内大量重复的异步操作的时候,单纯的使用Promise仍然会导致资源的浪费。因为每一次Promise的调用都会生成一个全新的对象,而这些对象在执行完毕后,如果没有被其他变量引用,就会被垃圾回收机制销毁。这就带来了内存的频繁分配与回收,消耗了大量的资源。

解决方案

为了解决这个问题,我们可以考虑实现一种机制,能够在某个Promise对象处于pending状态时,保存这个对象的状态和结果,并在之后的方法调用中直接返回这个结果。这样,当我们在短时间内多次请求同一个资源时,只需要发起一次真正的异步请求,其他的请求都可以直接返回之前的结果。这就是Promise状态共享的概念

具体来说,我们可以通过闭包保存一个Promise对象的引用,并在该Promise对象的状态变为fulfilled或rejected之后,清除这个引用。这样,在每次调用Promise的方法时,都先检查这个引用是否存在,如果存在直接返回该Promise对象。如果不存在,就发起新的异步请求,并将新的Promise对象的引用保存起来。

代码实现

/**
 * 当前任务没有完成时 多次调用会共享同一个promise实例状态
 * @param task 需要运行的异步任务
 * @returns 
 */
export const sharePromise = (task: () => Promise<any>) => {
    let _shareP: Promise<any> | null = null;
    return () => {
        if (!_shareP) {
            _shareP = new Promise((_res, _rej) => {
                task().then((res) => {
                    _res(res)
                }).catch((err) => {
                    _rej(err)
                }).finally(() => {
                    _shareP = null;
                })
            });
        }
        return _shareP;
    };
};

通过这种方式,我们可以将短时间内多次发起的相同异步请求合并为一个,大大减少了资源的消耗,提高了应用程序的性能。

同时,通过共享Promise的状态和结果,可以确保所有的请求都获得相同的数据,使得应用程序的行为更加稳定和可预测。

验证

使用setTimeout声明一个异步任务

const t = () => {
    return new Promise((_res, _rej) => {
        console.log('执行任务')
        setTimeout(() => {
            _res(1)
        }, 1000)
    })
}

case 1

多次调用异步任务 等待结果返回

const sp = sharePromise(t);
const p1 = sp()
p1.then(res => { console.log('p1 res: ', res) });

const p2 = sp()
p2.then(res => { console.log('p2 res: ', res) });

const p3 = sp();
p3.then(res => { console.log('p3 res: ', res) });

console.log('p1 === p2', p1 === p2);
console.log('p2 === p3', p2 === p3);

截屏2024-07-30 下午3.25.43.png

可以看到我们调用了三次异步任务 但是实际只执行了一次异步操作 返回的p1 p2 p3是相同的实例对象 三个异步的结果也被共享了。当第一次异步操作结束 promise状态发生变化后 三次调用都可以正确的获取返回值1。

case 2

const sp = sharePromise(t);
const p1 = sp()
p1.then(res => { console.log('111111: ', res) });

let p2: Promise<any>;
let p3: Promise<any>;
setTimeout(() => {
    p2 = sp()
    p2.then(res => { console.log('222222: ', res) });
}, 500)

setTimeout(() => {
    p3 = sp()
    p3.then(res => { console.log('333333: ', res) });
}, 2000)


setTimeout(()=>{
    console.log('p1 === p2', p1 === p2);
    console.log('p2 === p3', p2 === p3);
}, 4000)

异步任务执行耗时1s

p1 立即执行

p2 500m后在执行 期望p2复用p1的结果

p3 2s后在执行 此时p1应该已经结束 p3重新发起一次异步任务 不会复用p1的结果

运行结果如图:

截屏2024-07-30 下午3.46.00.png

可以看到和我们期望的是一致的。异步任务执行了两次。 p1 和 p2 是一个实例 状态共享。 p3是新的异步请求。 都可以正确的拿到返回结果。

github源码

share-promise

更多基于promise的 优化js运行时的解决方案 run-time-opti

本库长期维护更新...

系列文章

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant