JavaScript中XMLHttpRequest回调多次执行问题排查
问题现象
最开始以为代码中onreadystatechange里的if和else代码块都会被执行,添加debugger断点调试后,发现代码实际会多次运行,不清楚具体原因,复现代码如下:
复现代码
JS逻辑
function Submit(form) { var timer_starttime = document.getElementById("timer_starttime"); var timer_finishtime = document.getElementById("timer_finishtime"); if (wait_s.reportValidity() && wait_m.reportValidity()) { var xmlhttp = new XMLHttpRequest(); // new HttpRequest instance var theUrl = "/submit_program"; xmlhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { //document.getElementById("ajax_res").innerHTML = this.responseText; document.getElementById("success-alert").className = "alert alert-success alert-dismissible"; console.log(this.responseText); console.log("if"); debugger; } else { document.getElementById("error-alert").className = "alert alert-danger alert-dismissible"; console.log("else"); } }; xmlhttp.open("POST", theUrl); xmlhttp.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); xmlhttp.send(JSON.stringify({ "timer_finishtime": timer_finishtime.value, "timer_starttime": timer_starttime.value })); } return false; } console.log("end");
表单HTML
<form id="TimeForm" action="" method="POST"> ... <button type="submit" class="btn btn-primary" onclick="return Submit(this);">Save</button> </form>
问题原因
出现该现象的核心原因是对XMLHttpRequest的onreadystatechange事件触发逻辑存在认知偏差:
- 该事件并非在请求结束时仅触发1次,而是会在请求的每一个生命周期状态发生变化时触发,一个正常请求从初始化到完成,回调至少会触发4次。
XMLHttpRequest的readyState属性对应5个请求阶段:- 0:请求未初始化,尚未调用
open()方法 - 1:服务端连接已建立,
open()方法调用完成 - 2:请求已发送,
send()方法调用完成,已接收全部响应头 - 3:正在接收响应体,响应内容处于传输解析过程中
- 4:请求全部完成,整个响应接收完毕
- 0:请求未初始化,尚未调用
- 现有判断逻辑为:只要不满足
readyState == 4 && status == 200就进入else分支,请求处于0、1、2、3这几个中间状态触发回调时,都会进入else分支打印日志,等最后请求完成且状态码正常时才进入if分支,因此会观察到代码多次运行、if和else分支都有输出的现象。 - 该逻辑还存在额外问题:请求传输过程中就会给错误提示框添加显示样式,会导致请求正常时用户也能看到错误提示。
修复方案
调整回调里的判断顺序,先判断请求是否已经完成,只有请求完成后再根据状态码判断是成功还是失败,中间状态不做任何业务处理:
xmlhttp.onreadystatechange = function() { // 中间状态直接返回,不做处理 if (this.readyState !== 4) return; if (this.status === 200) { document.getElementById("success-alert").className = "alert alert-success alert-dismissible"; console.log(this.responseText); console.log("请求成功"); debugger; } else { // 只有请求完成但状态码异常时才显示错误提示 document.getElementById("error-alert").className = "alert alert-danger alert-dismissible"; console.log("请求失败,状态码:" + this.status); } };
内容的提问来源于stack exchange,提问作者Mehmet
相关产品推荐
相关产品推荐

