前言
在日常前端开发中,我们经常会遇到需要缓存请求的场景,比如解决重复请求、为什么不从代码优化呢,比如一个页面多个地方调用,你如果改,怎么改?父组件请求,一层一层传?首先是层级太多,其次是容易出bug。今天,我们就通过一个请求缓存函数的实现,来解决问题和深入理解JavaScript中的闭包及其应用。
闭包基础
首先,让我们简单回顾一下闭包的概念:
function outer() {
let count = 0;
return function inner() {
count++;
return count;
};
}
const counter = outer();
console.log(counter()); // 1
console.log(counter()); // 2
在这个例子中,inner函数能够访问outer函数的count变量,这就是闭包的基本表现。
请求缓存函数实现
现在,让我们来看一个实际的请求缓存函数实现,它充分利用了闭包的特性:
const createMemoReq = () => {
// 闭包内的缓存存储
const pendingRequests = new Map();
// 缓存项接口定义
const createCacheItem = (promise, cacheTimeMS) => ({
promise,
cacheTimeMS,
responseTimeMs: Infinity
});
return function memoReq(fn, cacheTimeMS = 0, context) {
return function (...args) {
// 生成包含上下文的缓存键
const key = `${fn.name}|${context ? JSON.stringify(context) : 'null'}|${JSON.stringify(args)}`;
// 检查缓存有效性
const checkCacheValid = () => {
const cache = pendingRequests.get(key);
if (!cache) return false;
const isExpired = (Date.now() - cache.responseTimeMs) >= cache.cacheTimeMS;
if (isExpired) pendingRequests.delete(key);
return !isExpired && cache;
};
// 返回有效缓存
if (checkCacheValid()) {
return pendingRequests.get(key).promise;
}
// 创建新请求
const executeRequest = () => {
const promise = fn.apply(context || this, args)
.then(response => {
pendingRequests.set(key, {
...pendingRequests.get(key),
responseTimeMs: Date.now()
});
return response;
})
.catch(error => {
pendingRequests.delete(key);
throw error;
});
pendingRequests.set(key, createCacheItem(promise, cacheTimeMS));
return promise;
};
return executeRequest();
};
};
};
闭包在实现中的应用
在这个实现中,我们充分利用了闭包的几个特性:
-
数据私有化:
pendingRequestsMap被封闭在createMemoReq的作用域内- 外部无法直接访问或修改缓存数据
-
状态保持:
- 每次调用
memoReq返回的函数都能访问同一个pendingRequests - 缓存状态在多次调用间得以保持
- 每次调用
-
函数工厂:
createMemoReq是一个工厂函数,可以创建多个独立的缓存实例
使用示例
使用前
export function getListFromRedis() {
return axios.get("/admin-api/stock/symbol/list-all");
}
使用后
export const getListFromRedis = memoReq( function getListFromRedis() {
return axios.get("/admin-api/stock/symbol/list-all");
}, 1000)
使用效果
// 第一次调用 - 发起实际请求
getListFromRedis().then(console.log);
// 3秒内再次调用 - 返回缓存结果
getListFromRedis().then(console.log);
// 3秒后调用 - 发起新请求
setTimeout(() => {
getListFromRedis().then(console.log);
}, 3001);
总结与展望
通过这个例子,我们看到了闭包在实际开发中的强大能力。这种实现方式简单有效,但仍有优化空间:
- 可以添加缓存大小限制
- 实现更复杂的缓存策略(如LRU)
- 使用更加便捷的装饰器模式尽可能小的改造
下期预告:总所周知这样使用会报错,下期我将分享如何通过Babel插件,用装饰器语法实现更优雅的请求缓存,就像这样:
@memoReq(3000)
async function getListFromRedis() {
return axios.get("/admin-api/stock-symbol/list-all");
}