如何取消挂起的 API 请求以显示正确的数据

Avatar of Georgi Nikoloff
Georgi Nikoloff

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

我最近需要在 React 中创建一个部件,该部件从多个 API 端点获取数据。当用户点击时,会获取新的数据并将其编组到 UI 中。但这导致了一些问题。

一个问题很快变得显而易见:如果用户点击速度足够快,当先前的网络请求得到解决时,UI 会在短时间内显示不正确、过时的数据。

我们可以 去抖动 我们的 UI 交互,但这从根本上无法解决我们的问题。过时的网络获取将得到解决并使用错误的数据更新我们的 UI,直到最终的网络请求完成并使用最终的正确状态更新我们的 UI。在较慢的连接上,问题变得更加明显。此外,我们还留下了无用的网络请求,浪费了用户的带宽。

我构建了一个示例来说明这个问题。它通过很酷的 Cheap Shark API 使用现代的fetch()方法从 Steam 获取游戏交易。尝试快速更新价格限制,您将看到 UI 如何闪烁错误数据,直到最终稳定下来。

解决方案

事实证明,有一种方法可以使用AbortController中止挂起的 DOM 异步请求。您可以使用它来取消 HTTP 请求以及 事件侦听器

AbortController 接口表示一个控制器对象,允许您根据需要中止一个或多个 Web 请求。

Mozilla 开发者网络

AbortController API 非常简单:它公开了一个AbortSignal,我们将其插入我们的fetch()调用中,如下所示

const abortController = new AbortController()
const signal = abortController.signal
fetch(url, { signal })

从这里开始,我们可以调用abortController.abort()以确保我们的挂起获取被中止。

让我们重写我们的示例,以确保我们正在取消任何挂起的获取并将仅从 API 收到的最新数据编组到我们的应用程序中

代码大多相同,但有一些关键区别

  1. 它在<App />组件中的useRef中创建一个新的缓存变量abortController
  2. 对于每个新的获取,它都会使用新的AbortController初始化该获取并获取其对应的AbortSignal
  3. 它将获得的AbortSignal传递给fetch()调用。
  4. 它在下次获取时自行中止。
const App = () => {
 // Same as before, local variable and state declaration
 // ...

 // Create a new cached variable abortController in a useRef() hook
 const abortController = React.useRef()

 React.useEffect(() => {
  // If there is a pending fetch request with associated AbortController, abort
  if (abortController.current) {
    abortController.abort()
  }
  // Assign a new AbortController for the latest fetch to our useRef variable
  abortController.current = new AbortController()
  const { signal } = abortController.current

  // Same as before
  fetch(url, { signal }).then(res => {
    // Rest of our fetching logic, same as before
  })
 }, [
  abortController,
  sortByString,
  upperPrice,
  lowerPrice,
 ])
}

结论

就是这样!我们现在兼具两者的优势:我们对 UI 交互进行去抖动,并且手动取消过时的挂起网络获取。这样,我们就可以确保我们的 UI 只更新一次,并且只使用来自我们 API 的最新数据进行更新。