AJAX/Javascript函数首次调用失效,二次调用正常的原因排查
我正在研究AJAX请求,目前在JavaScript中为Twitter Bootstrap向导实现服务端验证。遇到了一个奇怪的问题:函数首次执行时不符合预期,第二次执行才会得到正确结果。
以下是简化后的代码结构:
function do_ajax_validate($user,$email) { $.ajax({url: "checkuser.php?field=username&query=" + $user, success: function(result){ if (result != 'Valid') { $("#userlabel").html(result); $("#userdiv").addClass("has-error"); $("#userdiv").removeClass("has-success"); $("#username").focus(); is_good[0] = false; } else { $("#userlabel").html($user + " is available!"); $("#userdiv").removeClass("has-error"); $("#userdiv").addClass("has-success"); is_good[0] = true; console.log(is_good[0]); } }}); console.log(is_good[0]); $.ajax({url: "checkuser.php?field=email&query=" + $email, success: function(result){ if (result != 'Valid') { $("#emaillabel").html(result); $("#emaildiv").addClass("has-error"); $("#emaildiv").removeClass("has-success") $("#email").focus() is_good[1] = false; } else { $("#emaillabel").html('Looks good!'); $("#emaildiv").removeClass("has-error"); $("#emaildiv").addClass("has-success") is_good[1] = true; } }}); console.log(is_good[0]); console.log(is_good[1]); if ((is_good[0])&&(is_good[1])) { return true; } else { return false; } }
控制台测试情况
- 传入可用的用户名/邮箱,首次调用输出:
do_ajax_validate('available_username', 'available@email.address'); false false false
- 二次调用相同内容,输出:
do_ajax_validate('available_username', 'available@email.address'); true true true
- 传入已占用的用户名/邮箱时,首次调用返回上次的结果:
do_ajax_validate('taken_username', 'taken@email.address'); true true true
- 二次调用才返回预期结果:
do_ajax_validate('available_username', 'available@email.address'); false false false
我已经折腾了好几个小时,试了各种方法还是这个问题,到底哪里出错了?
这个问题的核心是AJAX的异步特性——你现在的代码里,$.ajax默认是异步执行的,也就是说,当你调用do_ajax_validate时,函数会立刻发起AJAX请求,但不会等待请求完成,直接继续执行后面的console.log和返回逻辑。
第一次调用时,AJAX请求还在和服务器通信,is_good数组里的值还是之前的默认值(或者未初始化的undefined,会被判断为false),所以函数直接返回了false。等AJAX请求完成后,is_good才被修改为true,这时候第二次调用函数,AJAX请求还没完成前,is_good已经是上次修改后的true,所以返回true,而等这次AJAX完成后又更新了值,这就导致了“第二次才正确”的错觉。
修复方案:用异步/等待(async/await)处理AJAX请求
jQuery的$.ajax返回的是一个Deferred对象,它可以和async/await一起使用,这样就能让函数等待AJAX请求完成后再执行后续逻辑。修改后的代码如下:
async function do_ajax_validate($user, $email) { // 初始化is_good,避免依赖之前调用的残留值 let is_good = [false, false]; // 等待用户名验证请求完成 const userResult = await $.ajax({ url: `checkuser.php?field=username&query=${$user}` }); if (userResult !== 'Valid') { $("#userlabel").html(userResult); $("#userdiv").addClass("has-error").removeClass("has-success"); $("#username").focus(); is_good[0] = false; } else { $("#userlabel").html(`${$user} is available!`); $("#userdiv").removeClass("has-error").addClass("has-success"); is_good[0] = true; } // 等待邮箱验证请求完成 const emailResult = await $.ajax({ url: `checkuser.php?field=email&query=${$email}` }); if (emailResult !== 'Valid') { $("#emaillabel").html(emailResult); $("#emaildiv").addClass("has-error").removeClass("has-success"); $("#email").focus(); is_good[1] = false; } else { $("#emaillabel").html('Looks good!'); $("#emaildiv").removeClass("has-error").addClass("has-success"); is_good[1] = true; } console.log(is_good[0], is_good[1]); return is_good[0] && is_good[1]; }
使用注意事项
因为现在函数是async的,所以调用它的时候需要用await或者.then()来获取返回值:
// 比如在async函数里调用 async function handleValidation() { const isValid = await do_ajax_validate('available_username', 'available@email.address'); console.log(isValid); // 这里会得到正确的布尔值 } // 或者用.then() do_ajax_validate('available_username', 'available@email.address') .then(isValid => { console.log(isValid); });
其他可选方案:用Promise.all并行处理请求
如果两个验证请求不需要按顺序执行,可以用Promise.all同时发起,提升效率:
async function do_ajax_validate($user, $email) { let is_good = [false, false]; // 同时发起两个请求 const [userResult, emailResult] = await Promise.all([ $.ajax({url: `checkuser.php?field=username&query=${$user}`}), $.ajax({url: `checkuser.php?field=email&query=${$email}`}) ]); // 处理用户名验证结果 if (userResult !== 'Valid') { $("#userlabel").html(userResult); $("#userdiv").addClass("has-error").removeClass("has-success"); $("#username").focus(); is_good[0] = false; } else { $("#userlabel").html(`${$user} is available!`); $("#userdiv").removeClass("has-error").addClass("has-success"); is_good[0] = true; } // 处理邮箱验证结果 if (emailResult !== 'Valid') { $("#emaillabel").html(emailResult); $("#emaildiv").addClass("has-error").removeClass("has-success"); $("#email").focus(); is_good[1] = false; } else { $("#emaillabel").html('Looks good!'); $("#emaildiv").removeClass("has-error").addClass("has-success"); is_good[1] = true; } console.log(is_good[0], is_good[1]); return is_good[0] && is_good[1]; }
这样修改后,每次调用函数都会等待AJAX请求完成后再返回结果,就不会出现首次调用失效的问题了。另外,在函数内部初始化is_good也避免了它依赖之前调用的残留值,这也是你之前测试时“首次调用返回上次结果”的原因之一。
内容的提问来源于stack exchange,提问作者Zyro Labs

