同一个请求发送多次,如何保证获取的是最后一次的结果

📅 2026/7/13 17:03:28 👁️ 阅读次数 📝 编程学习
同一个请求发送多次,如何保证获取的是最后一次的结果

面试先给结论

同一个请求被连续发送多次时,要保证最终只采用最后一次请求的结果,常见做法有 3 种:

  1. 取消前一次请求,只保留最后一次
  2. 给每次请求打标记 / 序号,只处理最新那次响应
  3. 防抖 / 节流,从源头减少重复请求

实际项目里最稳妥的是:

请求取消 + 响应序号校验

这样即使前面的请求没成功取消,也不会污染最后的页面状态。


一、这个问题的本质是什么

这其实是典型的竞态问题(race condition)

比如搜索框联想:

  • 输入a,发出请求 A
  • 很快又输入ab,发出请求 B
  • 理论上页面应该显示ab的结果

但如果网络情况是:

  • 请求 B 先返回
  • 请求 A 后返回

那如果你不做处理,A 的旧结果就会把 B 的新结果覆盖掉。


二、最常见方案 1:取消前一次请求

这是最直接的。

思路

每次发新请求前:

  • 先把上一次请求取消
  • 只保留当前这次请求

这样就不会出现“旧请求回来覆盖新请求”的问题。


axios 示例

旧版:CancelToken

let cancelFn = null function fetchData(params) { if (cancelFn) { cancelFn('取消上一次请求') } return axios.get('/api/search', { params, cancelToken: new axios.CancelToken((c) => { cancelFn = c }) }) }

新版更推荐:AbortController

let controller = null async function fetchData(params) { if (controller) { controller.abort() } controller = new AbortController() try { const res = await fetch(`/api/search?q=${params}`, { signal: controller.signal }) const data = await res.json() return data } catch (err) { if (err.name === 'AbortError') { console.log('上一次请求已取消') return } throw err } }

面试怎么说

如果是同一个接口被频繁触发,比如搜索建议,我会优先在发起新请求前取消上一次请求,只保留最后一次请求。这样可以减少无效请求,也能避免旧响应覆盖新数据。


三、最稳的方案 2:请求序号 / 时间戳校验

这是非常经典的方案,面试里一定要会说。

思路

每次发请求时给它一个递增 id:

  • 请求 A:id = 1
  • 请求 B:id = 2
  • 请求 C:id = 3

响应回来时判断:

  • 只有当前响应 id 等于“最新请求 id”,才更新页面
  • 否则直接丢弃

这样即使旧请求没有被取消,它返回了也不会生效。


示例

let currentRequestId = 0 async function fetchData(keyword) { const requestId = ++currentRequestId const res = await fetch(`/api/search?q=${keyword}`) const data = await res.json() if (requestId !== currentRequestId) { // 说明这不是最后一次请求,丢弃结果 return } // 只有最后一次请求才能更新页面 render(data) }

优点

  • 实现简单
  • 不依赖底层请求库是否支持取消
  • 对任何异步请求都适用

面试表达

我会给每次请求加一个递增序号,响应回来后判断它是不是最新请求。如果不是最新的,就直接忽略。这样可以保证页面最终只使用最后一次请求的结果。

这句话很标准。


四、最佳实践:取消请求 + 序号校验一起用

这是最完整、最专业的回答。

因为现实里有两个问题:

  1. 取消请求不一定总能完全阻止后续逻辑
  2. 有些场景底层请求无法真正取消

所以更稳的方式是:

  • 发新请求前先取消旧请求
  • 响应回来后再做一次“是否最新”的判断

示例

let controller = null let latestRequestId = 0 async function fetchData(keyword) { if (controller) { controller.abort() } controller = new AbortController() const requestId = ++latestRequestId try { const res = await fetch(`/api/search?q=${keyword}`, { signal: controller.signal }) const data = await res.json() if (requestId !== latestRequestId) return render(data) } catch (err) { if (err.name === 'AbortError') return throw err } }

为什么这套最稳

  • 取消请求:减少无效网络开销
  • 序号校验:防止旧结果误更新页面

五、源头治理:防抖

如果这个场景是输入框实时搜索,除了保证“只取最后一次结果”,还应该考虑:

减少根本没必要发出的请求

这时就用防抖


示例

function debounce(fn, delay = 300) { let timer = null return function (...args) { clearTimeout(timer) timer = setTimeout(() => { fn.apply(this, args) }, delay) } } const search = debounce((keyword) => { fetchData(keyword) }, 300)

适用场景

  • 搜索建议
  • 输入联想
  • 筛选条件变化后请求列表

但要注意

防抖只是减少请求次数,不等于一定能解决竞态问题。
所以更严谨的说法是:

防抖用来减少重复请求,取消请求和响应校验用来保证最终结果正确。


六、Vue / React 里常见怎么做


Vue 里

常见于:

  • watch(inputValue, ...)
  • 输入框搜索
  • 列表筛选

思路:

  • 监听关键字变化
  • 防抖触发请求
  • 每次请求前取消前一次
  • 返回后校验是否最新

React 里

常见于:

  • useEffect
  • 搜索输入
  • 条件筛选

会配合:

  • AbortController
  • 闭包里的 requestId
  • 清理函数里取消请求

七、如果面试官问“只用时间戳可以吗”

可以。

比如每次请求记录一个时间戳:

let latestTime = 0 async function fetchData() { const time = Date.now() latestTime = time const res = await fetch('/api/data') const data = await res.json() if (time !== latestTime) return render(data) }

但是更推荐用:

  • 递增requestId

因为更明确,也不会受时间精度影响。


八、如果是 Promise,怎么保证只取最后一次

可以封装一个takeLatest思路。

示例

function createTakeLatest(asyncFn) { let latestId = 0 return async function (...args) { const id = ++latestId const result = await asyncFn(...args) if (id !== latestId) return return result } }

使用:

const fetchLatest = createTakeLatest(async (keyword) => { const res = await fetch(`/api/search?q=${keyword}`) return res.json() }) fetchLatest('a').then(res => { if (res) render(res) })

九、面试标准回答模板

你可以直接这样答:

这个问题本质上是多个异步请求并发导致的竞态问题。
比如同一个接口连续请求多次时,前一次请求可能比后一次更晚返回,从而把最新结果覆盖掉。
为了解决这个问题,我一般会有三层处理:

  1. 发新请求前取消上一次请求,减少无效网络开销;
  2. 给每次请求加递增序号或时间戳,响应回来时判断它是不是最新请求,只有最新请求才能更新页面;
  3. 如果是输入联想这类高频触发场景,再配合防抖,从源头减少重复请求。

实际项目里我更推荐“取消请求 + 请求序号校验”一起用,这样即使旧请求没有成功取消,也能保证最终页面只展示最后一次请求的结果。


十、30 秒短答版

同一个请求连续发送多次时,我会把它当作竞态问题处理。
常见做法是:发新请求前取消旧请求,并且给每次请求加一个 requestId,响应回来时只有 id 等于最新值才更新页面。
如果是搜索框这类场景,还会再加防抖减少无效请求。
这样可以保证最终拿到并展示的是最后一次请求的结果。


十一、一句话总结

保证只取最后一次结果,核心不是让前面的请求“不返回”,而是让前面的结果“即使返回也无效”。