Skip to content

搜索框怎么处理请求竞态? ​

输入用防抖,请求可取消,过期的响应直接丢掉。界面把「还没输入」「加载中」「没有结果」「失败」分成四种状态,不要共用一个空列表。

原理 ​

每次输入都请求,既浪费也容易乱序:先发出去的「裤」可能后返回,盖住后来的「裤子」。先等输入停一段时间:

js
const search = debounce((keyword) => {
  load(keyword)
}, 300)

只防抖还不够。上一次请求如果更慢,仍会后到。用 AbortController 取消它,或者给每次请求一个递增序号,响应回来时序号对不上就丢弃:

js
let latest = 0
let controller = null

async function load(keyword) {
  const current = ++latest
  controller?.abort()
  controller = new AbortController()
  const response = await fetch(`/api/search?q=${encodeURIComponent(keyword)}`, {
    signal: controller.signal
  })
  const data = await response.json()
  if (current !== latest) return
  setResult(data)
}

两种可以一起用:取消是为了少做无用功,序号是为了即使取消没生效(例如不支持的环境,或响应来自内存缓存)也不会把旧数据写上屏幕。

空关键字不要发请求,直接清空结果。请求失败和「没有匹配」分开显示。输入法组合输入期间(compositionstart 到 compositionend)先不要搜索,否则拼音中间态会打出一串请求。

防抖函数创建一次,放在组件实例上。卸载时 cancel,并 abort 未完成的请求。见 防抖与节流。

追问 ​

要不要把结果缓存起来?

同一个关键字短时间反复出现时可以。缓存键是规范化之后的关键字加上筛选条件。缓存要有容量,接口数据会过期时还要有失效时间。先把竞态做对,再谈缓存。

防抖时间怎么定?

看输入习惯和接口成本。常见是 200–400ms。时间是产品手感,不是一个有标准答案的常数。面试里说出你怎么选的即可。

易错点 ​

  • 只在 then 里 setResult,忘记看这次请求是不是最新的。
  • AbortError 当成失败提示弹出来。取消是预期行为。
  • 加载中把上一次的结果清成空列表,界面会闪。新的结果返回之前可以保留旧列表,并标上加载状态。

相关 ​