form.checkvalidity() 不能防竞态,因它仅瞬时校验且不绑定请求生命周期;用户连点提交时两次均可能通过校验,但后发请求可能先返回并覆盖前次结果。

form.checkValidity() 为什么不能防竞态
它只做瞬时校验,不绑定请求生命周期。用户点两次提交按钮,两次都通过 checkValidity(),但后一次请求可能先返回、覆盖前一次结果——校验通过 ≠ 请求安全。
常见错误是把 checkValidity() 当成“锁”,在它返回 true 后立刻禁用按钮并发请求,却没处理网络延迟下多个请求并发抵达的问题。
- 校验只是前置动作,和后续 fetch 的执行完全解耦
- 移动端 Safari 在
input或blur事件中调用它才稳定,submit 时才查容易错过实时错误 - 它不会触发
invalid事件,所以无法自动更新 UI 提示,得手动补逻辑
如何用 AbortController 拦住重复提交的请求
不是禁用按钮,而是让旧请求“死在路上”。每次点击提交,都新建一个 AbortController,并中止上一个未完成的请求。
关键不在“发”,而在“停”:新请求启动前必须调 controller.abort(),否则旧请求还在后台跑,响应回来照样进 .then()。
- 每个表单实例配独立
AbortController,不跨组件或窗口共享 -
fetch必须传入{ signal: controller.signal },否则 abort 无效 - 在
catch中检查err.name === 'AbortError',遇到就静默忽略,不 setState、不弹提示 - 不要在
finally里重置控制器——abort 后 signal 已失效,需全新实例
requestSubmit() + preventDefault 是校验与提交的正确衔接方式
requestSubmit() 不是提交命令,它是“触发原生校验 + 只有通过才派发 submit 事件”的桥梁。很多人误以为它能绕过监听器直接发请求,结果校验通过后页面跳转了。
必须和 event.preventDefault() 配合使用,且只能在 <form></form> 元素上调用。否则要么报 TypeError,要么校验失败时没提示、成功时又跳转。
- 在
submit监听器里调requestSubmit()是错的——它会再次触发 submit,造成无限循环 - 正确姿势:按钮 click 里调
form.requestSubmit(),然后在 form 的submit事件里preventDefault()+fetch - 旧版 Android WebView 不支持,降级用
form.reportValidity()+ 手动checkValidity()判断
含文件上传时 FormData 的竞态陷阱
FormData 看似无害,但在快速连续提交含文件的表单时,容易因浏览器复用内部 buffer 或异步读取 file input 导致字段丢失、空值被跳过、甚至 append() 失败却不报错。
更隐蔽的是:同一个 FormData 实例被多次用于不同 fetch,第二次发出去的可能是第一次的缓存数据——因为部分浏览器对文件字段做了懒加载。
- 每次提交都新建
new FormData(form),别复用实例 - 含文件时绝对不要手动设置
Content-Type,否则 boundary 被破坏,后端收不到文件 - 校验前先检查
form.elements中 type="file" 字段是否真有files.length > 0,避免空文件提交成功但业务失败 - 服务端收到 multipart 请求后,也要校验文件字段是否存在,不能只信前端传来的 key
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











