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

一图胜千文:

背景:

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

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

解决方案

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

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

代码实现

javascript 复制代码
/**
 * 当前任务没有完成时 多次调用会共享同一个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声明一个异步任务

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

case 1

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

javascript 复制代码
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);

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

case 2

javascript 复制代码
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的结果

运行结果如图:

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

github源码

share-promise

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

本库长期维护更新...

系列文章

相关推荐
excel11 分钟前
webpack 核心编译器 十四 节
前端
excel18 分钟前
webpack 核心编译器 十三 节
前端
腾讯TNTWeb前端团队7 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
uhakadotcom10 小时前
视频直播与视频点播:基础知识与应用场景
后端·面试·架构
范文杰11 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪11 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪11 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy12 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom12 小时前
快速开始使用 n8n
后端·面试·github
uhakadotcom12 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试