同时存在点击提交与回车提交时需要共用同一把锁
别名: 回车重复提交 · 双通道锁 · click and enter lock
概念解释
表单往往既能点按钮提交,也能在字段里按回车提交。两通道若各绑各的处理函数,各有一把「进行中」旗标,就会在同一瞬间各发一次。共用同一把锁指:无论点击还是回车,先抢同一把进程内锁,抢不到的那次直接丢掉。只把按钮设为 disabled 却不挡住表单的 submit 事件,回车仍能再送一封。
机制
浏览器默认:焦点在单行输入里按回车,触发表单 submit,再点到提交按钮上。用户的习惯是填完回车,或填完再点,或回车同时鼠标已经在点——后一种在慢响应时尤其多。若点击路径在按钮上锁,回车路径走 form.onsubmit 且不看那把锁,两条路径就是两次请求。键盘重复(按住回车)还会在锁生效前打出一串 keydown。锁必须在所有入口的最前面,而且是同步的:用「等 setState 再禁用」会把锁推迟到下一帧,回车已经出去了。服务端去重是后盾,但客户端双通道仍会制造双倍负载和短暂的双成功态。
怎么研究
在带可调延迟的表单上,同时点按钮并按回车,或按住回车。记录请求条数。对照:只锁按钮、锁按钮且 preventDefault submit、入口函数最前面一把同步锁。
自变量:锁放在哪一层、是否 preventDefault、输入法回车是否另发事件。 因变量:每意图请求数、双成功提示。
自动化分别测 click 和 keydown 会都通过。必须在同一时刻叠加两条通道。
边界
多提交按钮的表单(保存草稿 / 发布)不能共用到「整页一把锁」把另一意图也锁死,但同一意图的所有入口仍要共锁。对话框里回车等于点默认按钮,这是第三条通道,也要进同一把锁。无表单的 div 按钮没有 submit 事件,回车通道可能根本不存在,问题缩小到按钮自己的键与点击。
怎么落地
- 把发出请求的函数做成单入口,点击、回车、submit 都调用它,函数第一行同步抢锁。
- 在 form submit 上 preventDefault,避免浏览器再走一遍原生提交。
- 不要只靠下一帧的 disabled 挡回车。
- 验证:焦点在输入框时按住回车并同时点击提交。网络日志里这一意图只能有一条请求。