网络并发管理
本页内容

网络并发管理

在构建 Web 应用程序时,管理网络请求可能是一项艰巨的任务。确保数据是最新的以及处理同时请求的挑战通常会导致应用程序中出现复杂的逻辑,以处理中断和竞争条件。Remix 通过自动执行网络管理、模仿和扩展 Web 浏览器的直观行为,简化了此过程。

为了帮助理解 Remix 的工作原理,请记住来自 全栈数据流 的内容,即在 form 提交之后,Remix 将从加载器中获取新的数据。这被称为重新验证。

与浏览器行为的自然对齐

Remix 处理网络并发的机制很大程度上受到 Web 浏览器在处理文档时默认行为的启发。

  • 浏览器链接导航:当您在浏览器中点击一个链接,然后在页面转换完成之前点击另一个链接时,浏览器会优先处理最新的 action。它会取消初始请求,只关注最后点击的链接。

    • Remix 的方法:Remix 以相同的方式管理客户端导航。当在 Remix 应用程序中点击链接时,它会启动对与目标 URL 关联的每个加载器的获取请求。如果另一个导航中断初始导航,Remix 会取消先前的获取请求,确保只有最新的请求继续进行。
  • 浏览器表单提交:如果您在浏览器中启动表单提交,然后很快再次提交另一个表单,浏览器会忽略第一次提交,只处理最后一次提交。

    • Remix 的方法:Remix 在处理表单时模仿这种行为。如果表单被提交,另一个提交在第一个提交完成之前发生,Remix 会取消原始获取请求。然后,它会等待最新的提交完成,然后再触发页面重新验证。

并发提交和重新验证

虽然标准浏览器一次只能处理一个导航和表单提交请求,但 Remix 提升了这种行为。与导航不同,使用 useFetcher 可以同时进行多个请求。

Remix 被设计为能够高效地处理对服务器 action 的多个表单提交以及并发重新验证请求。它确保一旦有新数据可用,状态就会立即更新。但是,Remix 也会通过阻止在其他 action 造成竞争条件时提交陈旧数据来避免潜在的陷阱。

例如,如果三个表单提交正在进行,其中一个完成了,Remix 会立即使用该数据更新 UI,而无需等待另外两个完成,这样 UI 就可以保持响应和动态。随着剩余提交的完成,Remix 继续更新 UI,确保显示最新的数据。

为了帮助理解一些可视化效果,以下是图表中使用的符号的键值对。

  • |:提交开始
  • ✓:Action 完成,数据重新验证开始
  • ✅:重新验证的数据已提交到 UI
  • ❌:请求被取消
submission 1: |----✓-----✅
submission 2:    |-----✓-----✅
submission 3:             |-----✓-----✅

但是,如果后续提交的重新验证在之前的提交完成之前完成,Remix 会丢弃之前的数据,确保 UI 中只反映最新的信息。

submission 1: |----✓---------❌
submission 2:    |-----✓-----✅
submission 3:             |-----✓-----✅

由于提交 (2) 的重新验证启动较晚,但比提交 (1) 提前完成,因此提交 (1) 的请求被取消,只有提交 (2) 的数据提交到 UI。它是在提交 (1) 之后被请求的,因此它更有可能包含 (1) 和 (2) 中的更新值。

潜在的陈旧数据

虽然这种情况不太可能发生在您的用户身上,但在基础设施不一致的情况下,用户仍然有可能在极少数情况下看到过时的数据。即使 Remix 会取消过时数据的请求,这些请求最终还是会到达服务器。在浏览器中取消请求只是释放了该请求的浏览器资源,它无法“追赶”并阻止它到达服务器。在极少数情况下,取消的请求可能会在中断的 `action` 的重新验证完成之后更改数据。请参考以下图表

     👇 interruption with new submission
|----❌----------------------✓
       |-------✓-----✅
                             👆
                  initial request reaches the server
                  after the interrupting submission
                  has completed revalidation

现在用户看到的数据与服务器上的数据不同。请注意,这个问题既非常罕见,也存在于默认的浏览器行为中。初始请求到达服务器的时间晚于第二次提交和重新验证的时间的可能性在任何网络和服务器基础设施中都是不可预期的。如果您的基础设施存在此问题,您可以将时间戳与表单提交一起发送,并编写服务器逻辑以忽略过时的提交。

示例

在组合框等 UI 组件中,每次按键操作都可能触发一个网络请求。管理这种快速连续的请求可能很棘手,特别是为了确保显示的结果与最新的查询匹配。但是,使用 Remix,此挑战将自动处理,确保用户看到正确的结果,而无需开发人员微观管理网络。

import type { LoaderFunctionArgs } from "@remix-run/node"; // or cloudflare/deno
import { json } from "@remix-run/node"; // or cloudflare/deno

export async function loader({
  request,
}: LoaderFunctionArgs) {
  const { searchParams } = new URL(request.url);
  const cities = await searchCities(searchParams.get("q"));
  return json(cities);
}

export function CitySearchCombobox() {
  const fetcher = useFetcher<typeof loader>();

  return (
    <fetcher.Form action="/city-search">
      <Combobox aria-label="Cities">
        <ComboboxInput
          name="q"
          onChange={(event) =>
            // submit the form onChange to get the list of cities
            fetcher.submit(event.target.form)
          }
        />

        {/* render with the loader's data */}
        {fetcher.data ? (
          <ComboboxPopover className="shadow-popup">
            {fetcher.data.length > 0 ? (
              <ComboboxList>
                {fetcher.data.map((city) => (
                  <ComboboxOption
                    key={city.id}
                    value={city.name}
                  />
                ))}
              </ComboboxList>
            ) : (
              <span>No results found</span>
            )}
          </ComboboxPopover>
        ) : null}
      </Combobox>
    </fetcher.Form>
  );
}

应用程序只需要知道如何查询数据以及如何呈现数据,Remix 会处理网络。

结论

Remix 为开发人员提供了一种直观、基于浏览器的网络请求管理方法。通过模仿浏览器行为并在必要时对其进行增强,它简化了并发、重新验证和潜在的竞争条件的复杂性。无论您是构建一个简单的网页还是一个复杂的 Web 应用程序,Remix 都能确保您的用户交互流畅、可靠且始终保持最新状态。

文档和示例许可证: MIT