同一个请求发送多次,如何保证获取的是最后一次的结果 面试先给结论同一个请求被连续发送多次时要保证最终只采用最后一次请求的结果常见做法有 3 种取消前一次请求只保留最后一次给每次请求打标记 / 序号只处理最新那次响应防抖 / 节流从源头减少重复请求实际项目里最稳妥的是请求取消 响应序号校验这样即使前面的请求没成功取消也不会污染最后的页面状态。一、这个问题的本质是什么这其实是典型的竞态问题race condition。比如搜索框联想输入a发出请求 A很快又输入ab发出请求 B理论上页面应该显示ab的结果但如果网络情况是请求 B 先返回请求 A 后返回那如果你不做处理A 的旧结果就会把 B 的新结果覆盖掉。二、最常见方案 1取消前一次请求这是最直接的。思路每次发新请求前先把上一次请求取消只保留当前这次请求这样就不会出现“旧请求回来覆盖新请求”的问题。axios 示例旧版CancelTokenlet cancelFn null function fetchData(params) { if (cancelFn) { cancelFn(取消上一次请求) } return axios.get(/api/search, { params, cancelToken: new axios.CancelToken((c) { cancelFn c }) }) }新版更推荐AbortControllerlet 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请求 Aid 1请求 Bid 2请求 Cid 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) }优点实现简单不依赖底层请求库是否支持取消对任何异步请求都适用面试表达我会给每次请求加一个递增序号响应回来后判断它是不是最新请求。如果不是最新的就直接忽略。这样可以保证页面最终只使用最后一次请求的结果。这句话很标准。四、最佳实践取消请求 序号校验一起用这是最完整、最专业的回答。因为现实里有两个问题取消请求不一定总能完全阻止后续逻辑有些场景底层请求无法真正取消所以更稳的方式是发新请求前先取消旧请求响应回来后再做一次“是否最新”的判断示例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) })九、面试标准回答模板你可以直接这样答这个问题本质上是多个异步请求并发导致的竞态问题。比如同一个接口连续请求多次时前一次请求可能比后一次更晚返回从而把最新结果覆盖掉。为了解决这个问题我一般会有三层处理发新请求前取消上一次请求减少无效网络开销给每次请求加递增序号或时间戳响应回来时判断它是不是最新请求只有最新请求才能更新页面如果是输入联想这类高频触发场景再配合防抖从源头减少重复请求。实际项目里我更推荐“取消请求 请求序号校验”一起用这样即使旧请求没有成功取消也能保证最终页面只展示最后一次请求的结果。十、30 秒短答版同一个请求连续发送多次时我会把它当作竞态问题处理。常见做法是发新请求前取消旧请求并且给每次请求加一个 requestId响应回来时只有 id 等于最新值才更新页面。如果是搜索框这类场景还会再加防抖减少无效请求。这样可以保证最终拿到并展示的是最后一次请求的结果。十一、一句话总结保证只取最后一次结果核心不是让前面的请求“不返回”而是让前面的结果“即使返回也无效”。