AJAX 请求缓存策略
实现 AJAX 请求缓存,避免重复获取数据
问题
如何避免 AJAX 数据请求重新获取?实现请求缓存和去重机制。
解答
方案一:内存缓存
// 简单的内存缓存
const cache = new Map();
async function fetchWithCache(url, options = {}) {
const cacheKey = `${url}_${JSON.stringify(options)}`;
// 检查缓存
if (cache.has(cacheKey)) {
return cache.get(cacheKey);
}
// 发起请求
const response = await fetch(url, options);
const data = await response.json();
// 存入缓存
cache.set(cacheKey, data);
return data;
}
// 使用
fetchWithCache('/api/users').then(console.log);
fetchWithCache('/api/users').then(console.log); // 命中缓存
方案二:带过期时间的缓存
class RequestCache {
constructor(ttl = 60000) {
this.cache = new Map();
this.ttl = ttl; // 默认 60 秒过期
}
get(key) {
const item = this.cache.get(key);
if (!item) return null;
// 检查是否过期
if (Date.now() > item.expiry) {
this.cache.delete(key);
return null;
}
return item.data;
}
set(key, data) {
this.cache.set(key, {
data,
expiry: Date.now() + this.ttl
});
}
clear() {
this.cache.clear();
}
}
const cache = new RequestCache(30000); // 30 秒过期
async function fetchWithTTL(url) {
const cached = cache.get(url);
if (cached) return cached;
const response = await fetch(url);
const data = await response.json();
cache.set(url, data);
return data;
}
方案三:请求去重(防止并发重复请求)
const pendingRequests = new Map();
async function fetchWithDedup(url, options = {}) {
const key = `${url}_${JSON.stringify(options)}`;
// 如果有相同的请求正在进行,返回同一个 Promise
if (pendingRequests.has(key)) {
return pendingRequests.get(key);
}
// 创建请求 Promise
const promise = fetch(url, options)
.then(res => res.json())
.finally(() => {
// 请求完成后移除
pendingRequests.delete(key);
});
pendingRequests.set(key, promise);
return promise;
}
// 同时发起多个相同请求,只会实际请求一次
Promise.all([
fetchWithDedup('/api/users'),
fetchWithDedup('/api/users'),
fetchWithDedup('/api/users')
]).then(console.log);
方案四:结合缓存和去重
class SmartFetcher {
constructor(options = {}) {
this.cache = new Map();
this.pending = new Map();
this.ttl = options.ttl || 60000;
}
async fetch(url, options = {}) {
const key = this.getKey(url, options);
// 1. 检查缓存
const cached = this.getCache(key);
if (cached) return cached;
// 2. 检查是否有进行中的请求
if (this.pending.has(key)) {
return this.pending.get(key);
}
// 3. 发起新请求
const promise = this.doFetch(url, options, key);
this.pending.set(key, promise);
return promise;
}
async doFetch(url, options, key) {
try {
const response = await fetch(url, options);
const data = await response.json();
// 存入缓存
this.setCache(key, data);
return data;
} finally {
this.pending.delete(key);
}
}
getKey(url, options) {
return `${url}_${JSON.stringify(options)}`;
}
getCache(key) {
const item = this.cache.get(key);
if (!item) return null;
if (Date.now() > item.expiry) {
this.cache.delete(key);
return null;
}
return item.data;
}
setCache(key, data) {
this.cache.set(key, {
data,
expiry: Date.now() + this.ttl
});
}
// 手动清除缓存
invalidate(url) {
for (const key of this.cache.keys()) {
if (key.startsWith(url)) {
this.cache.delete(key);
}
}
}
}
// 使用
const fetcher = new SmartFetcher({ ttl: 30000 });
fetcher.fetch('/api/users').then(console.log);
fetcher.fetch('/api/users').then(console.log); // 命中缓存或复用请求
// 数据更新后清除缓存
fetcher.invalidate('/api/users');
方案五:使用 localStorage 持久化缓存
function fetchWithStorage(url, ttl = 60000) {
const key = `fetch_cache_${url}`;
// 检查 localStorage
const cached = localStorage.getItem(key);
if (cached) {
const { data, expiry } = JSON.parse(cached);
if (Date.now() < expiry) {
return Promise.resolve(data);
}
localStorage.removeItem(key);
}
// 发起请求
return fetch(url)
.then(res => res.json())
.then(data => {
localStorage.setItem(key, JSON.stringify({
data,
expiry: Date.now() + ttl
}));
return data;
});
}
关键点
- 内存缓存:使用 Map 存储请求结果,适合单页应用
- 请求去重:相同请求进行中时复用 Promise,避免并发重复请求
- TTL 过期:设置缓存有效期,防止数据过时
- 持久化缓存:使用 localStorage 跨页面保持缓存
- 缓存失效:数据变更时主动清除相关缓存
目录