返回文章
JavaScript约 3 分钟阅读

搜索请求乱序了?用 AbortController 收尾

用户输入很快,网络返回很慢。取消过期请求,并防止旧结果覆盖新结果,让搜索状态更可靠。

GUAN前端开发手记
示例文章

收藏保存在此浏览器,可在归档页查看。

文章目录4 个章节

用户先输入 “re”,随后输入 “react”。第二个请求可能更早完成,而第一个请求稍后返回,把正确结果覆盖。这个问题与请求快慢有关,不能靠“平时测试没遇到”来避免。

取消不再需要的请求

AbortController 为 fetch 提供一个取消信号。新的搜索开始时,先取消旧请求。

let controller;
let requestId = 0;

async function search(query) {
  controller?.abort();
  controller = new AbortController();
  const currentId = ++requestId;

  try {
    const response = await fetch(
      `/api/search?q=${encodeURIComponent(query)}`,
      { signal: controller.signal }
    );
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }
    const data = await response.json();
    if (currentId !== requestId) return;
    // 用当前请求的结果更新页面。
    renderResults(data);
  } catch (error) {
    if (error instanceof DOMException &&
        error.name === 'AbortError') return;
    if (currentId !== requestId) return;
    showSearchError('搜索失败,请重试');
  }
}

这是应用结构示例:/api/search、renderResults 和 showSearchError 需要由实际项目实现。本站文章搜索使用本地索引,不依赖这个接口。

为什么还检查请求编号

取消可以减少无用的工作;请求编号则约束“只有最新请求能更新页面”。当流程包含无法被 signal 取消的处理步骤时,这道检查尤其有用。

fetch 遇到 HTTP 404 或 500 通常不会自动 reject,因此还要检查 response.ok。取消请求是预期行为,不应当显示成用户需要处理的错误。

防抖解决另一件事

防抖减少请求发起次数,取消和编号处理过期结果。它们可以配合,但不能互相替代。即使每 300 毫秒才发一次请求,响应仍可能乱序。

清理也是逻辑的一部分

在组件销毁、页面切换或用户清空搜索词时,取消请求并使旧编号失效。若界面还有 loading 状态,也需要只让当前请求更新它,避免旧请求的 finally 提前关闭新的加载提示。

读到这里,不妨动手试一试。
把一个概念变成能运行的小实验。

找一篇笔记

输入关键词,或浏览全部文章。

按 Tab 选择文章Esc 关闭