1、引言
通常我们在开发表单提交数据的时候会使用JavaScript API fetch
发送接口请求。这个时候,后端会说「多次点击提交按钮的时候记得不要重复发送接口请求」。于是我们会在按钮加一个防抖函数或者在接口发起请求加个loading,置灰按钮,等到请求结束再恢复按钮,以此来解决重复发送请求问题。
虽然这些做法都能够解决问题,但不够优雅。今天我们就换个思路来看下如何阻止表单多次提交请求。
2、数据请求的变迁
先看一段简单的html表单发送请求代码:
<form method="post" action="/post">
<label for="name">昵称</label>
<input id="name" name="name" />
<button>提交</button>
</form>
点击提交按钮,可以将数据发送到服务端,但现在已经很少有这样的写法了,因为它会刷新浏览器,这种远古文明的方式已经被现在的Ajax所代替。
于是我们改为用JavaScript来编写表单提交代码:
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();
}
通过获取表单元素,监听submit
事件,点击提交按钮时使用fetch
发送接口请求,实现服务端的数据发送。其中event.preventDefault
可以阻止浏览器的刷新,这就是典型的异步请求,相比表单的同步提交体验会好很多,至少浏览器在不刷新的情况下完成前端交互。
3、fetch发送与中止请求
当快速点击提交按钮时,会大量请求接口,这不是我们想要看到的,排除防抖和loading我们尝试另一种解决方法。
事实上,我们最终的目的是为了阻止发送多次请求到服务端,避免服务器过载,那么我们只需要在发送接口请求时,中止上一次请求,而通过fetch可能轻松做到这一点。
Fetch是下一代Ajax技术,通过Promise方式来处理数据,具有更简洁明了的API,更加简单易用。目前大部分现代浏览器对fetch都已支持。
通过fetch文档可以获知中止接口请求的方法。
const controller = new AbortController();
fetch(url, { signal: controller.signal });
controller.abort()
只需要创建AbortController控制器,向fetch提供AbortController信号就可以随时调用abort方法进行中止请求。
我们通过加入AbortController来实现阻止表单多次提交。
const pendingForms = new WeakMap(); // 创建集合
const form = document.querySelector("form");
form.addEventListener("submit", handleSubmit);
function handleSubmit(event) {
const form = event.currentTarget;
const previousController = pendingForms.get(form); // 获取form对象
// 存在则中止请求
if (previousController) {
previousController.abort();
}
const controller = new AbortController(); // 创建AbortController实例
pendingForms.set(form, controller); // 添加到集合中
fetch(form.action, {
method: form.method,
body: new FormData(form),
signal: controller.signal,
}).then(() => {
pendingForms.delete(form); // 请求结束,删除form对象以便再次发起请求
});
event.preve应用Default(); // 阻止浏览器刷新
}
上述代码将每次表单请求的对象存储在WeakMap
中,如果在当前请求还未结束再次发起请求,就会中上当前请求,执行后面发起的请求,从而阻止表单进行多次提交。
Tips:存储form对象为什么使用WeakMap而不是Map?
WeakMap的API与Map类似,而WeakMap的key所引用的对象都是弱引用,只要对象的其他引用被删除,垃圾回收机制就会释放该对象占用的内存,从而避免内存泄漏。
这里还有个问题,就是频繁中止请求控制台会不断抛出异常信息。
想必作为完全主义者的我们,这种异常是不可容忍的。我们只需要在fetch增加catch
进行异常捕获即可解决这个问题。
fetch(form.action, {
method: form.method,
body: new FormData(form),
signal: controller.signal,
}).then(() => {
pendingForms.delete(form); // 请求结束,删除form对象以便再次发起请求
}).catch((error) => {
// 错误信息为“AbortError” 则跳出,不打印到控制台
if (error.name === "AbortError") return;
throw error;
});
4、总结
1、Fetch作为下一代Ajax技术,相比XMLHttpRequest更加简单易用。作为前端开发者,要时刻保持技术的自我update。
2、AbortController 为一个控制器对象,配合fetch,允许我们根据需要中止一个或多个 Web 请求。
3、别再使用防抖和loading方式解决表单多次提交问题,要从根源上解决问题,用魔法打败魔法。