一图胜千文:
背景:
在实际的软件开发过程中,异步操作是非常常见的场景。这些异步操作涵盖了各种各样的情况,其中最常见的就是网络请求和客户端与服务器之间的交互。这些操作往往需要消耗一定的时间和系统资源
,比如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源码
更多基于promise的 优化js运行时的解决方案 run-time-opti
本库长期维护更新...