本文分析JavaScript中Ajax同步请求(async:false)导致浏览器UI线程阻塞、界面假死的原因,并探讨两种解决方案:使用setTimeout延迟执行以缓解阻塞,以及推荐采用Deferred异步处理机制从根本上避免同步请求带来的性能问题。
问题的起因
一、问题的起因
今日处理一项需求,涉及用户个人中心功能:点击按钮以刷新当前积分。此场景需采用Ajax同步请求。代码编写过程迅速,大致实现如下:
/**
* 异步当前用户积分 by zgw 20161216
* @return {[type]} [description]
*/
function flushIntegralSum() {
//点击按钮刷新前修改按钮的文案,已经去掉点击事情,防止多次点击
$("#flushbutton").replaceWith('<a style="color:#3fb0ff;font-size:14px;" href="javascript:void(0);" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" id="flushbutton">正在刷新</a>');
$.ajax({
url:'URL',
type:'post',
async:false,
// data:{},
success:function(json){
json = eval('('+json+')');
if(json.url){window.location.href=json.url;return;}
$("#flushbutton").replaceWith('<a style="color:#3fb0ff;font-size:14px;" href="javascript:void(0);" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" onclick="flushFreeSum();" id="flushbutton">刷新积分</a>');
if(json.code!=1){
alert(json.msg);
}else{
$("#free_sum").html(json.free_sum);
}
return;
}
});
}
预期中简单的功能在运行时出现异常。点击刷新积分按钮后,界面文案未更新为“正在刷新”,尽管Ajax请求已发送。检查网页源码发现,JavaScript在发起请求前移除了按钮的点击事件(onclick),并在请求成功后恢复。然而,页面上的文案并未随之改变,导致界面显示与代码逻辑不一致。
问题原因分析
二、了解问题原因
经排查,问题根源在于Ajax请求设置了“async:false”(同步模式)。若改为异步请求,则不会出现此问题。那么,为何同步请求会导致代码失效呢?
原因在于浏览器的渲染(UI)线程与JavaScript线程是互斥的。当执行耗时的JavaScript操作时,页面渲染会被阻塞。异步Ajax请求不会阻塞UI线程,但同步请求会暂停后续代码执行及渲染线程。即使DOM操作语句在发起请求之前,同步请求也会迅速阻塞UI线程,使其无法执行更新操作,从而导致界面显示失效。
解决方案
三、解决问题
1. 使用setTimeout解决:将Ajax代码包裹在setTimeout中,利用浏览器重新调度线程来执行,从而解决问题。代码如下:
function flushIntegralSum() {
//点击按钮刷新前修改按钮的文案,已经去掉点击事情,防止多次点击
$("#flushbutton").replaceWith('<a style="color:#3fb0ff;font-size:14px;" href="javascript:void(0);" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" id="flushbutton">正在刷新</a>');
setTimeout(function(){
$.ajax({
url:'URL',
type:'post',
async:false,
// data:{},
success:function(json){
json = eval('('+json+')');
if(json.url){window.location.href=json.url;return;}
$("#flushbutton").replaceWith('<a style="color:#3fb0ff;font-size:14px;" href="javascript:void(0);" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" onclick="flushFreeSum();" id="flushbutton">刷新积分</a>');
if(json.code!=1){
alert(json.msg);
}else{
$("#free_sum").html(json.free_sum);
}
return;
}
});
},0)
}
将setTimeout的第二个参数设为0,浏览器会在设定的最小时间后执行该函数。
此方法虽能解决文案更新问题,但若在请求期间需要显示旋转的GIF图片以提示“更新中”,会发现图片静止不动。这是因为虽然同步请求被延迟执行,但在其执行期间,UI线程仍被阻塞,连GIF动画也无法渲染,看起来像静态图片。结论是:setTimeout仅治标不治本,相当于将同步请求“稍稍”异步化,随后仍会进入同步阻塞的困境。该方法仅适用于请求前操作耗时极短的场景。
2. 使用Deferred解决:通过jQuery的Deferred对象处理异步请求,从根本上避免同步阻塞问题。
以上为解决JS Ajax同步请求造成浏览器假死问题的全部内容,希望能提供参考。