标准答案

  1. 先为请求生成稳定 cacheKey,通常由 url、method、params、body 中的关键字段组成。
  2. 用 pendingMap 保存进行中的请求,相同 key 直接返回已有 Promise。
  3. 用 cacheMap 保存成功结果和过期时间,命中未过期缓存时直接返回数据。
  4. 请求完成后要从 pendingMap 删除,避免长期占用内存。
  5. 实际项目还要考虑错误是否缓存、手动失效、鉴权维度、POST 是否允许缓存和数据一致性。

题目解析

去重和缓存不是一回事。去重解决的是“同一时刻重复请求”,缓存解决的是“短时间内重复读取同一结果”。两者可以组合,但状态要分开管理。

cacheKey 设计很关键。如果 key 太粗,会把不同用户、不同参数的请求错误合并;如果 key 太细,又无法命中缓存。

失败请求通常不应该默认缓存,否则一次临时网络错误会在有效期内持续返回失败。进行中的请求无论成功失败都要清理 pendingMap。

代码示例

下面的模块同时支持进行中请求去重和成功结果 TTL 缓存。

JavaScript
function createRequestCache({ ttl = 30000 } = {}) {
  const pendingMap = new Map()
  const cacheMap = new Map()

  function request(key, fetcher) {
    const cached = cacheMap.get(key)
    const now = Date.now()

    if (cached && cached.expireAt > now) {
      return Promise.resolve(cached.data)
    }

    if (pendingMap.has(key)) {
      return pendingMap.get(key)
    }

    const task = Promise.resolve()
      .then(fetcher)
      .then(data => {
        cacheMap.set(key, {
          data,
          expireAt: Date.now() + ttl
        })
        return data
      })
      .finally(() => {
        pendingMap.delete(key)
      })

    pendingMap.set(key, task)
    return task
  }

  function invalidate(key) {
    cacheMap.delete(key)
  }

  return {
    request,
    invalidate
  }
}

常见误区

  • 只做结果缓存,没有复用进行中的 Promise,首屏多个组件仍然会同时发重复请求。
  • cacheKey 没有包含用户、参数或请求体,导致不同请求拿到同一份缓存。
  • 请求失败后没有清理 pendingMap,后续请求一直复用失败状态或卡住。

作者信息