点击提交按钮毫无反应:校验链自锁与上游请求长时间 pending 的排查
点击提交按钮毫无反应:校验链自锁与上游请求长时间 pending 的排查
表单页点「发布」,页面纹丝不动:URL 不变、没有新标签页、Network 面板里一条请求都没有、window.onerror 和 unhandledrejection 都是空。按钮既没有 disabled 属性,也没有 loading 态。这种情况下最容易得出的错误结论是"前端对账号做了静默拦截"或"按钮被遮罩盖住了",但 elementFromPoint 回验命中的确实是按钮本身。
真实原因通常在两条独立的链路上,任意一条断掉,表现都完全一样。
一、先确认事件处理器还在不在
很多提交按钮不是 addEventListener,而是内联 onclick="submitForm(0)"。这类代码常见的写法是:
function newBlogSubmit() {
$('.write-blog-publish-btn').removeAttr('onclick'); // 关键一行
submitFlag = 1;
var checkFlag = checkCount();
if (!checkFlag) { return false; }
// ...真正提交
}
函数一进来就把自己的 onclick 摘掉,同时把模块级标志位置 1。所以:
- 第一次点击如果因为别的原因失败,函数已经执行过一半,
onclick没了; - 第二次点击哪怕元素还在,也没有处理器可触发,表现就是"点了没反应"。
排查时不要只看按钮外观,直接读属性:
document.querySelector('#submit-btn').getAttribute('onclick') // null 说明已被摘掉
document.querySelector('#submit-btn').className // 带 -no 后缀 = 已被锁死
二、用探针确认上游请求是不是在 pending
即使 onclick 还在,submitForm 里往往还有一层前置校验,例如先请求一个协议签署状态接口,回调里才真正提交:
function checkDevProSign(callback, needSign) {
$.ajax({
url: '.../v1/member/get-personal-agreement',
xhrFields: { withCredentials: true },
success: function (res) { callback(); }
});
}
这类接口可能长时间不返回:既不 resolve 也不 reject,success 回调永远不执行,newBlogSubmit 也就永远不会被调用。页面此时没有任何错误提示,因为压根没走到错误分支。
判据要自己造,不要靠猜。在页面上下文里跑一个带超时的探针:
const c = new AbortController();
setTimeout(() => c.abort(), 6000);
fetch('https://example.com/rest/.../member/get-personal-agreement',
{ credentials: 'include', signal: c.signal })
.then(r => r.text().then(t => console.log(r.status, t)))
.catch(e => console.log('probe failed:', e.name));
三种结果对应三种处理:
| 探针结果 | 含义 | 处理 |
|---|---|---|
| 200 且返回正常 JSON | 接口没问题,问题在第二层 | 去查 checkCount() |
403 / no request_csrf found |
会话没带 CSRF token | 先刷新会话再提交,不是按钮的错 |
| 6 秒超时无响应 | 上游接口 pending | 等,或换网络,不要连续点 |
三、模块级标志位自锁
第二层的 submitFlag 一旦置 1,checkCount() 就不再返回真值:
function checkCount() {
if (submitFlag) return 0; // 静默返回 0
return validateAll() ? 1 : 0;
}
newBlogSubmit 拿到 0 之后 return false,同时给按钮加上禁用样式的类名,且再也不会恢复。这时候无论怎么点、用 JS el.click() 还是用真实鼠标事件 Input.dispatchMouseEvent,都一样没反应——因为拦截发生在业务逻辑内部,不是 DOM 层。
直接读函数返回值是最快的判据:
checkCount() // 1 = 正常;0 = submitFlag 已置位,页面已自锁
checkAllContent(1) // true = 内容校验通过
四、恢复策略
关键点:每点一次失败,都可能把 submitFlag 再置一次位,所以"多点几次试试"只会让状态更难恢复。
正确的顺序是:
- 先跑接口探针,确认上游返回 200;
- 再读
checkCount(),为 1 才允许点击; checkCount()已经是 0 的,唯一解是整页重载,重新填写内容。所有表单内容都要重来,但这一步的成本通常只有几秒,远低于继续排查"为什么点了没反应"。
五、成功判据不是 URL 变化
单页应用的提交成功后不一定改 URL。真实判据是:
- 新开的
processcomplete类结果页,或 - 列表里新增一条记录(带服务器返回的 ID)。
如果自动化脚本用"URL 变了"当成功判据,会把成功判成失败;更糟的是,某些脚本的页面匹配逻辑在 URL 失效后会回退到第一个标签页,读到的其实是另一个页面,于是得出结论"提交后什么都没发生"。
小结的三条判据
getAttribute('onclick')为 null → 处理器已被自己摘掉;checkCount()返回 0 → 模块级 flag 自锁,必须重载页面;- 接口探针 6 秒无响应 → 上游 pending,等待而不是重试。
三条都正常却仍然没反应时,再去怀疑 DOM 遮挡和事件合成,顺序不要反过来。
- 点赞
- 收藏
- 关注作者
评论(0)