前端请求去重如何设计?重复点击和并发相同请求怎么处理?
简化版
前端请求去重是避免同一时间发出多份语义相同的请求。常见方案有按钮禁用、防抖节流、in-flight map 复用同一个 Promise、请求取消、短时间缓存和服务端幂等。设计时要先区分“查询类请求”和“写入类请求”:查询请求可以复用或缓存,写入请求不能简单合并,必须依赖业务幂等键和服务端兜底。
详细版
请求去重的核心是为请求生成稳定 key,例如 method + url + params + body 中影响结果的字段。
| 场景 | 推荐处理 | 说明 |
|---|---|---|
| 搜索框输入 | 防抖 + 取消旧请求 | 避免无效请求 |
| 页面重复加载同接口 | in-flight 复用 | 多个组件共享结果 |
| 按钮重复提交 | 禁用按钮 + 幂等键 | 服务端必须兜底 |
| 短时间重复查询 | TTL 缓存 | 减少后端压力 |
const pending = new Map<string, Promise<unknown>>()
function requestOnce<T>(key: string, fetcher: () => Promise<T>): Promise<T> {
if (pending.has(key)) return pending.get(key) as Promise<T>
const p = fetcher().finally(() => pending.delete(key))
pending.set(key, p)
return p
}
去重不是少发请求这么简单,写接口尤其要服务端幂等兜底。
完整版教学
一、为什么会出现重复请求
前端重复请求很常见。用户快速点击按钮,组件重复挂载,路由切换后又回来,多个组件都依赖同一份数据,搜索框每输入一个字符就请求一次,这些都会制造重复流量。
重复请求带来的问题包括浪费带宽、后端压力增加、页面状态被旧响应覆盖,以及写接口重复提交。
二、先区分读请求和写请求
读请求一般没有副作用,比如查询列表、获取详情、拉配置。它们适合复用、缓存和合并。
写请求有副作用,比如创建订单、提交表单、支付确认。它们不能只靠前端合并,必须由后端按业务 idempotency key 保证幂等。
三、in-flight 复用
in-flight 表示请求已经发出但还没完成。如果同一个 key 的请求再次出现,就返回同一个 Promise。
function buildKey(url: string, params: Record<string, unknown>) {
return `${url}?${new URLSearchParams(params as Record<string, string>)}`
}
请求完成后要从 map 中删除,否则会变成永久缓存。
四、请求 key 怎么设计
key 设计决定去重是否正确。
| 字段 | 是否常纳入 key |
|---|---|
| method | 是 |
| url/path | 是 |
| query params | 是 |
| body | 写请求谨慎 |
| auth user | 多用户场景要考虑 |
| headers | 影响结果时纳入 |
如果 key 太粗,会把不同请求错误合并;key 太细,则去重效果差。
五、和缓存的区别
in-flight 去重只复用“正在进行”的请求;缓存则复用“已经完成”的结果。
缓存需要 TTL、失效和刷新策略。去重通常不需要长期保留结果,只要请求结束就释放。
六、写请求要靠幂等
重复提交订单不能只靠按钮 disabled,因为用户可以刷新、重试、脚本请求或网络层重放。
写接口应带业务幂等键:
fetch('/api/order', {
method: 'POST',
headers: { 'Idempotency-Key': orderDraftId }
})
后端用这个 key 判断是否已经处理过。
七、常见误区与追问
- 误区:前端禁用按钮就能防重复提交。 用户可以绕过页面或网络重试,后端必须做幂等。
- 误区:所有相同 URL 请求都能合并。 query、body、用户身份和 header 可能影响响应。
- 误区:pending map 不清理也没关系。 不清理会造成内存增长和结果长期复用错误。
- 追问:请求失败后 pending 怎么办? 使用 finally 删除 key,让后续请求可以重新发起。
- 追问:读请求和写请求去重有什么区别? 读请求可复用结果,写请求要依赖幂等键和业务状态。
- 追问:多个组件请求同一接口怎么优化? 共享请求层或数据层,用 in-flight map 复用 Promise。
八、加强记忆
请求去重记成“同路请求共坐一班车”。读请求可以合并和缓存,写请求要幂等;key 要准,pending 要清,旧响应不能乱覆盖新状态。