什么是缓存?
在计算机科学中,缓存是一种在计算机内存中暂存数据的技术,以加快对这些数据的访问速度。通过缓存,可以把经常用到的数据暂存在高速缓存中,以便下次访问时可以更快地获取。这种技术被广泛应用于计算机的各个领域,包括数据库、网页浏览器等。
为什么需要缓存?
当我们的系统需要频繁地访问同一个数据时,每次都从磁盘或者网络获取数据显然效率很低。因此,我们需要将这些数据暂存到内存中,以提高访问速度。这就是缓存的作用。
需求
1、有白名单,标记仅对哪些接口做缓存处理
2、在5秒对同一个接口的请求,只会发起一次,但是其他重复的请求的响应体依旧有效(并且在5秒后清除相应缓存)
同一个 url ,一样的入参,一样的请求方式为同一个接口
代码实现
下面是一个使用缓存的例子:
const cache = {}; // 缓存对象
function fetchData(url) {
if (cache[url]) { // 如果缓存中存在该数据
return cache[url]; // 直接返回缓存中的数据
} else { // 如果缓存中不存在该数据
const data = fetchDataFromDiskOrNetwork(url); // 从磁盘或者网络中获取数据
cache[url] = data; // 将数据存入缓存
return data; // 返回数据
}
}
这段代码中,cache 是一个对象,用来存储数据的缓存。fetchData 函数接收一个 url 参数,用来指定要获取数据的位置。首先,它会检查 cache 中是否已经存在该数据,如果存在就直接返回缓存中的数据;如果不存在,则从磁盘或者网络中获取数据,并将数据存入 cache 中,然后返回数据。
这段代码的实现非常简单,但也有一些缺陷。缓存的数据是不会过期的,如果服务端的数据更新,就会导致缓存中的数据过期,从而访问到旧的数据。
为了解决这些问题,我们需要对缓存进行优化。下面是一个更加完善的缓存实现,它支持设置缓存过期时间,同时还支持清理缓存和白名单等功能。
// 缓存对象
const cache = {};
const whiteList = ["app/appPage/getPageVersionById"]; // 白名单
// 缓存配置,可以在调用 cacheFetch 时传入
const defaultConfig = {
cacheTime: 5 * 1000, // 缓存时间(毫秒)
};
export function cacheFetch(url, options = {}, config = {}) {
// 合并默认配置和传入配置
const trueConfig = Object.assign({}, defaultConfig, config);
// 如果不需要缓存,则直接调用 fetch
if (!shouldCache(url)) {
return window.fetch(url, options);
}
const key = getCacheKey(url, options);
const cached = cache[key];
// 如果缓存存在且未过期,则直接返回缓存
if (cached && !isExpired(cached, trueConfig.cacheTime)) {
return Promise.resolve(cached.data);
}
// 如果缓存不存在或已过期,则调用 fetch,并将结果加入缓存
const promise = window.fetch(url, options)
.then((data) => {
// 缓存时间到期后自动清除缓存
setTimeout(() => {
delete cache[key];
}, trueConfig.cacheTime);
// 将数据存入缓存
cache[key] = { data, timestamp: Date.now() };
return data;
});
// 将 promise 存入缓存,以便多次调用时能返回同一个 promise
cache[key] = { promise, timestamp: Date.now() };
return promise;
}
// 判断是否需要缓存
function shouldCache(url) {
// 只对在白名单中的接口进行缓存
for (let i = 0; i < whiteList.length; i++) {
if (url.includes(whiteList[i])) {
return true;
}
}
return false;
}
// 获取缓存键名
function getCacheKey(url, options) {
return JSON.stringify({ url, options });
}
// 判断缓存是否过期
function isExpired(cached, cacheTime) {
return Date.now() - cached.timestamp > cacheTime;
}
不难发现,上述的代码在网络响应较慢的时候,我们在 return Promise.resolve(cached.data); 的时候,data 可能是 undefined 。所以我们可以给请求加一个队列,如果同时发起5个请求,那么我们只发起一次,其他的 4 次都等同一个 Promise 返回。
// 缓存对象
const cache = {};
const whiteList = ['app/xxx']; // 白名单
// 队列
const pendingRequests = {};
// 缓存配置,可以在调用 fetchWithCache 时传入
const defaultConfig = {
cacheTime: 5 * 1000, // 缓存时间(毫秒)
capacity: 50, // 缓存容量
};
// 获取缓存键名
function getCacheKey(url, options) {
return JSON.stringify({ url, options });
}
// 判断缓存是否过期
function isExpired(cached, cacheTime) {
return !!(Date.now() - cached.timestamp > cacheTime);
}
// 判断是否需要缓存
function shouldCache(url) {
// 只对在白名单中的接口进行缓存
// eslint-disable-next-line no-plusplus
for (let i = 0; i < whiteList.length; i++) {
if (url.includes(whiteList[i])) {
return true;
}
}
return false;
}
// 清理缓存
function cleanCache(count) {
const entries = Object.entries(cache);
// 按缓存时间排序
entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
// 删除旧的缓存
// eslint-disable-next-line no-plusplus
for (let i = 0; i < count && i < entries.length; i++) {
delete cache[entries[i][0]];
}
}
export function fetchWithCache(url, options = {}, config = {}) {
// 合并默认配置和传入配置
const finalConfig = Object.assign({}, defaultConfig, config);
// 如果缓存不在容量范围内,则清理一部分缓存
if (Object.keys(cache).length >= finalConfig.capacity) {
cleanCache(finalConfig.capacity / 2);
}
// 如果不需要缓存,则直接调用 fetch
if (!shouldCache(url)) {
return window.fetch(url, options);
}
const key = getCacheKey(url, options);
const cached = cache[key];
// 如果缓存存在且未过期,则直接返回缓存
if (cached && !isExpired(cached, finalConfig.cacheTime)) {
return Promise.resolve(cached.data);
}
// 检查是否有等待的请求
if (pendingRequests[key]) {
return pendingRequests[key].then(data => {
// 缓存时间到期后自动清除缓存
setTimeout(() => {
delete cache[key];
}, finalConfig.cacheTime);
// 将数据存入缓存
cache[key] = { data, timestamp: Date.now() };
return data.clone();
});
}
// 如果缓存不存在或已过期,则调用 fetch,并将结果加入缓存
const promise = window.fetch(url, options).then(data => {
// 缓存时间到期后自动清除缓存
setTimeout(() => {
delete cache[key];
}, finalConfig.cacheTime);
// 将数据存入缓存
cache[key] = { data: data.clone(), timestamp: Date.now() };
return data.clone();
});
// 将请求添加到等待列表中
pendingRequests[key] = promise;
// 将 promise 存入缓存,以便多次调用时能返回同一个 promise
// cache[key] = { promise, timestamp: Date.now() };
return promise;
}
总结
以上就是本篇博客的全部内容。我们通过这段代码的解析,了解了缓存的一些基本知识和实现方式,以及如何进行缓存的清理和管理。缓存虽然可以提升性能,但也有一些需要注意的地方。比如,需要控制缓存容量和缓存时间,避免缓存过期或者过多占用内存等问题。
最后,需要注意的是,缓存并不是万能的。在某些情况下,缓存可能会导致错误的结果。因此,在使用缓存时,需要权衡利弊,避免产生不必要的问题。