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 跨页面保持缓存
  • 缓存失效:数据变更时主动清除相关缓存