如果您曾经使用 JavaScript API 来增强表单提交,那么您很可能不小心引入了重复请求/竞争条件错误。今天,我将向您介绍这个问题以及我为避免它而提出的建议。 fetch (如果你喜欢的话,视频在最后) 让我们考虑一个非常基本的 带有一个输入和一个提交按钮。 HTML 表格 <form method="post"> <label for="name">Name</label> <input id="name" name="name" /> <button>Submit</button> </form> 当我们点击提交按钮时,浏览器将刷新整个页面。 请注意单击提交按钮后浏览器如何重新加载。 页面刷新并不总是我们想要为用户提供的体验,因此一个常见的替代方法是使用 向表单的“提交”事件添加事件侦听器,防止默认行为,并使用 API 提交表单数据。 JavaScript fetch 一种简单的方法可能类似于下面的示例。 页面(或组件)挂载后,我们抓取表单 DOM 节点,添加一个使用表单构造获取请求 事件监听器 , , 和 ,在处理程序的末尾,我们调用事件的 方法。 fetch 行动 方法 数据 preventDefault() const form = document.querySelector('form'); form.addEventListener('submit', handleSubmit); function handleSubmit(event) { const form = event.currentTarget; fetch(form.action, { method: form.method, body: new FormData(form) }); event.preventDefault(); } 现在,在任何 JavaScript 能手开始向我发推文之前,我会讨论 GET 与 POST 和请求正文以及 不管其他什么,我只想说,我知道。我故意让 请求保持简单,因为那不是主要焦点。 内容类型 fetch 这里的关键问题是 。此方法阻止浏览器执行加载新页面和提交表单的默认行为。 event.preventDefault() 现在,如果我们查看屏幕并点击提交,我们可以看到页面没有重新加载,但我们确实在网络选项卡中看到了 HTTP 请求。 请注意,浏览器不会重新加载整个页面。 不幸的是,通过使用 JavaScript 来阻止默认行为,我们实际上引入了默认浏览器行为所没有的错误。 当我们使用纯 然后你很快地点击提交按钮很多次,你会注意到除了最近的一个之外所有的网络请求都变成了红色。这表明它们已被取消,只有最近的请求才会得到满足。 HTML 如果我们将其与 JavaScript 示例进行比较,我们将看到所有请求都已发送,并且所有请求都已完成且没有被取消。 这可能是一个问题,因为尽管每个请求可能需要不同的时间,但它们的解决顺序可能与发起时的顺序不同。这意味着如果我们为这些请求的解析添加功能,我们可能会出现一些意想不到的行为。 例如,我们可以创建一个变量来为每个请求递增(“ ”)。每次我们运行 函数时,我们都可以增加总计数并捕获当前数量以跟踪当前请求(“ ”)。 totalRequestCount handleSubmit thisRequestNumber 当 请求解决时,我们可以将其相应的编号记录到控制台。 fetch const form = document.querySelector('form'); form.addEventListener('submit', handleSubmit); let totalRequestCount = 0 function handleSubmit(event) { totalRequestCount += 1 const thisRequestNumber = totalRequestCount const form = event.currentTarget; fetch(form.action, { method: form.method, body: new FormData(form) }).then(() => { console.log(thisRequestNumber) }) event.preventDefault(); } 现在,如果我们多次点击那个提交按钮,我们可能会看到打印到控制台的不同数字乱序:2、3、1、4、5。这取决于网络速度,但我想我们都同意这并不理想。 考虑这样一种情况,用户连续触发多个 请求,完成后,您的应用程序会用他们的更改更新页面。由于请求解析顺序不正确,用户最终可能会看到不准确的信息。 fetch 这在非 JavaScript 世界中不是问题,因为浏览器会取消任何先前的请求并在最近的请求完成后加载页面,加载最新版本。但是页面刷新并不那么性感。 对于 JavaScript 爱好者来说,好消息是我们可以同时拥有 和一致的用户界面! 性感的用户体验 我们只需要做更多的跑腿工作。 如果查看 API 文档,您会发现可以使用 和 选项的 属性中止获取。它看起来像这样: fetch AbortController fetch signal const controller = new AbortController(); fetch(url, { signal: controller.signal }); 通过向 请求提供 的信号,我们可以在触发 的 方法时随时取消请求。 fetch AbortContoller AbortContoller abort 您可以在 JavaScript 控制台中看到更清晰的示例。尝试创建一个 ,发起 请求,然后立即执行 方法。 AbortController fetch abort const controller = new AbortController(); fetch('', { signal: controller.signal }); controller.abort() 您应该立即看到一个异常打印到控制台。在 Chromium 浏览器中,它应该说,“未捕获(承诺)DOMException:用户中止了请求。”如果您浏览“网络”选项卡,您应该会看到状态文本为“(已取消)”的失败请求。 考虑到这一点,我们可以将 添加到表单的提交处理程序中。逻辑如下: AbortController 首先,检查任何先前请求的 。如果存在,则中止它。 AbortController 接下来,为当前请求创建一个 ,它可以在后续请求中中止。 AbortController 最后,当请求解决时,删除其对应的 。 AbortController 有几种方法可以做到这一点,但我将使用 来存储每个提交的 DOM 节点与其各自的 之间的关系。提交表单时,我们可以相应地检查和更新 。 WeakMap <form> AbortController WeakMap const pendingForms = new WeakMap(); function handleSubmit(event) { const form = event.currentTarget; const previousController = pendingForms.get(form); if (previousController) { previousController.abort(); } const controller = new AbortController(); pendingForms.set(form, controller); fetch(form.action, { method: form.method, body: new FormData(form), signal: controller.signal, }).then(() => { pendingForms.delete(form); }); event.preventDefault(); } const forms = document.querySelectorAll('form'); for (const form of forms) { form.addEventListener('submit', handleSubmit); } 关键是能够将中止控制器与其相应的表单相关联。使用表单的 DOM 节点作为 的键是一种方便的方法。 WeakMap 有了它,我们可以将 的信号添加到 请求,中止任何以前的控制器,添加新控制器,并在完成后删除它们。 AbortController fetch 希望这一切都有意义。 现在,如果我们多次点击该表单的提交按钮,我们可以看到除了最近的请求之外的所有 API 请求都被取消了。 这意味着响应该 HTTP 响应的任何函数都将按照您的预期运行。 现在,如果我们使用与上面相同的计数和日志记录逻辑,我们可以点击提交按钮七次,并且会在控制台中看到六个异常(由于 )和一个“7”的日志。 AbortController 如果我们再次提交并留出足够的时间来解决请求,我们会在控制台中看到“8”。如果我们多次按下提交按钮,我们将继续以正确的顺序看到异常和最终请求计数。 如果您想添加更多逻辑以避免在请求中止时在控制台中看到 DOMExceptions,您可以在 请求之后添加一个 块并检查错误名称是否与“ ”匹配: fetch .catch() AbortError fetch(url, { signal: controller.signal, }).catch((error) => { // If the request was aborted, do nothing if (error.name === 'AbortError') return; // Otherwise, handle the error here or throw it back to the console throw error }); 关闭 整篇文章的重点是 JavaScript 增强的表单,但在您创建 请求时包含一个 可能是个好主意。真的太糟糕了,它还没有内置到 API 中。但希望这向您展示了一个包含它的好方法。 fetch AbortController 还值得一提的是,这种方法并不能防止用户多次点击提交按钮。按钮仍然可以点击,请求仍然被触发,它只是提供了一种更一致的处理响应的方式。 不幸的是,如果用户向提交按钮 垃圾邮件,这些请求仍会转到您的后端,并且可能会消耗大量不必要的资源。 发送 一些天真的解决方案可能是禁用提交按钮,使用 ,或者仅在先前的请求解决后创建新请求。我不喜欢这些选项,因为它们依赖于减慢用户体验并且只在客户端工作。 去抖动 他们不会通过脚本请求解决滥用问题。 为了解决对服务器的过多请求造成的滥用,您可能需要设置一些 .这超出了本文的范围,但值得一提。 速率限制 还值得一提的是,速率限制并不能解决重复请求、竞争条件和不一致的 UI 更新等原始问题。理想情况下,我们应该同时使用两者来覆盖两端。 无论如何,这就是我今天的全部内容。如果您想观看涵盖同一主题的视频,请观看此视频。 https://www.youtube.com/watch?v=w8ZIoLnh1Dc&embedable=true 非常感谢您的阅读。如果您喜欢这篇文章,请 .这是支持我的最好方式之一。你也可以 或者 如果您想知道新文章何时发布。 分享它 注册我的时事通讯 在推特上关注我 最初发表于 austingil.com .