Jquery ajax 同步阻塞引起的UI线程阻塞问题


Posted in Javascript onNovember 17, 2015

最近做一个项目,遇到了一个问题同步ajax引起的ui线程阻塞问题,下面把我的问题解决过程分享给大家。

事情起因是这样的,因为页面上有多个相似的异步请求动作,本着提高代码可重用性的原则,我封装了一个名为getData的函数,它接收不同参数,只负责获取数据,然后把数据return。基本的逻辑剥离出来是这样的:

function getData1(){
    var result;
    $.ajax({
      url : "p.php",
      async : false,
      success: function(data){
        result = data;
      }
    });

  return result;
} 

这里的ajax不能用异步的,否则函数返回时,result还未赋值,会出错。所以我加了async:false。看起来好像没什么问题。我调用这个函数可以正常的得到数据。

$(".btn1").click(function(){
    var data = getData1();
    alert(data);
});

接下来,要加另外一个功能,由于ajax请求有一定的耗时,所以我需要在发出请求前页面有个loading效果,即显示一张“正在加载”的gif图片,想必大家也都见过。所以我的处理函数就变成了这样:

$(".btn1").click(function(){
    $(".loadingicon").show();
    var data = getData1();
    $(".loadingicon").hide();
    alert(data);
});

请求之前显示loading图片,请求完成后把它隐藏。看起来也没什么问题。为了看清效果,我的p.php代码sleep了3秒,如下:

<?php
sleep(3);
echo ("aaaaaa");
?>

但是我运行的时候问题出现了,我点击按钮并未像预想的那样出现这个loading图片,页面什么反应也没有。排除良久找到了原因,就在async:false这里。

浏览器的渲染(UI)线程和js线程是互斥的,在执行js耗时操作时,页面渲染会被阻塞掉。当我们执行异步ajax的时候没有问题,但当设置为同步请求时,其他的动作(ajax函数后面的代码,还有渲染线程)都会停止下来。即使我的DOM操作语句是在发起请求的前一句,这个同步请求也会“迅速”将UI线程阻塞,不给它执行的时间。这就是代码失效的原因。

setTimeout解决阻塞问题

既然明白了问题在哪里,我们就来针对性想办法。为了不让同步ajax请求阻塞线程,我想到了setTimeout,把请求的代码放到sestTimeout中,让浏览器重启一个线程来操作,不就解决问题了吗?于是乎,我的代码就变成了这样:

$(".btn2").click(function(){
    $(".loadingicon").show();
    setTimeout(function(){
      $.ajax({
        url : "p.php",
        async : false,
        success: function(data){
          $(".loadingicon").hide();
          alert(data);
        }
      });
    }, 0);
}); 

setTimeout的第二个参数设为0,浏览器会在一个已设的最小时间后执行。不管三七二十一先运行起来看看。

结果loading图片显示出来了,但是!!!图片怎么不动呢,我明明是一张动态gif图。这个时候我很快就想到了,虽然同步请求延迟执行了,但是它执行期间还是会把UI线程给阻塞。这个阻塞相当牛逼,连gif图片都不动了,看起来像一张静态图片一样。

结论很明显,setTimeout治标不治本,相当于把同步请求“稍稍”异步了一下,接下来还是会进入同步的噩梦,阻塞线程。方案失败。

是时候用Deferred了

jQuery在1.5版本之后,引入了Deferred对象,提供的很方便的广义异步机制。详情可参看阮一峰老师的这篇文章http://www.ruanyifeng.com/blog/2011/08/a_detailed_explanation_of_jquery_deferred_object.html。

于是我用Deferred对象改写了代码,如下:

function getData3(){
    var defer = $.Deferred();
    $.ajax({
      url : "p.php",
      //async : false,
      success: function(data){
        defer.resolve(data)
      }
    });
    return defer.promise();
}  
$(".btn3").click(function(){
    $(".loadingicon").show();
    $.when(getData3()).done(function(data){
      $(".loadingicon").hide();
      alert(data);
    });
});

可以看到我在ajax请求中去掉了async:false,也就是说,这个请求又是异步的了。另外请注意success函数中的这一 句:defer.resolve(data),Deferred对象的resolve方法可传入一个参数,任意类型。这个参数可以在done方法中拿到, 所以我们异步请求来的数据就可以以这样的方式来返回了。

至此,问题得到了解决。Deferred对象如此强大且方便,我们可以好好利用它。

我的全部测试代码如下,有意的同学可以拿去测一下:

<button class="btn1">async:false</button>
<button class="btn2">setTimeout</button>
<button class="btn3">deferred</button>
<img class="loadingicon" style="position:fixed;left:50%;top:50%;margin-left:-16px;margin-top:-16px;display:none;" src=http://www.update8.com/Web/Jquery/"loading2.gif" alt="正在加载" />
<script>
  function getData1(){
    var result;
    $.ajax({
      url : "p.php",
      async : false,
      success: function(data){
        result = data;
      }
    });
    return result;
  }
  $(".btn1").click(function(){
    $(".loadingicon").show();
    var data = getData1();
    $(".loadingicon").hide();
    alert(data);
  });
  $(".btn2").click(function(){
    $(".loadingicon").show();
    setTimeout(function(){
      $.ajax({
        url : "p.php",
        async : false,
        success: function(data){
          $(".loadingicon").hide();
          alert(data);
        }
      });
    }, 0);
  });
  function getData3(){
    var defer = $.Deferred();
    $.ajax({
      url : "p.php",
      //async : false,
      success: function(data){
        defer.resolve(data)
      }
    });
    return defer.promise();
  }  
  $(".btn3").click(function(){
    $(".loadingicon").show();
    $.when(getData3()).done(function(data){
      $(".loadingicon").hide();
      alert(data);
    });
  });</script>

ps:$.ajax的参数描述

参数 描述

url 必需。规定把请求发送到哪个 URL。
data 可选。映射或字符串值。规定连同请求发送到服务器的数据。
success(data, textStatus, jqXHR) 可选。请求成功时执行的回调函数。
dataType 
可选。规定预期的服务器响应的数据类型。
默认执行智能判断(xml、json、script 或 html)。

Javascript 相关文章推荐
javascript 简单抽屉效果的实现代码
Mar 09 Javascript
js 金额文本框实现代码
Feb 14 Javascript
图片无缝滚动代码(向左/向下/向上)
Apr 10 Javascript
Bootstrap项目实战之子栏目资讯内容
Apr 25 Javascript
[Bootstrap-插件使用]Jcrop+fileinput组合实现头像上传功能实例代码
Dec 20 Javascript
jQuery插件zTree实现删除树节点的方法示例
Mar 08 Javascript
laravel5.3 vue 实现收藏夹功能实例详解
Jan 21 Javascript
vue绑定的点击事件阻止冒泡的实例
Feb 08 Javascript
JavaScript常用截取字符串的三种方式用法区别实例解析
May 15 Javascript
Vue.js实现双向数据绑定方法(表单自动赋值、表单自动取值)
Aug 27 Javascript
简单实现vue中的依赖收集与响应的方法
Feb 18 Javascript
JavaScript获取当前url路径过程解析
Dec 27 Javascript
jquery判断密码强度的验证代码
Apr 22 #Javascript
jquery实现邮箱自动填充提示功能
Nov 17 #Javascript
jQuery实现文本框邮箱输入自动补全效果
Nov 17 #Javascript
基于Jquery和html5的7款个性化地图插件
Nov 17 #Javascript
JavaScript设置、获取、清除单值和多值cookie的方法
Nov 17 #Javascript
每天一篇javascript学习小结(RegExp对象)
Nov 17 #Javascript
node.js抓取并分析网页内容有无特殊内容的js文件
Nov 17 #Javascript
You might like
php a simple smtp class
2007/11/26 PHP
鸡肋的PHP单例模式应用详解
2013/06/03 PHP
使用php记录用户通过搜索引擎进网站的关键词
2014/02/13 PHP
CI(CodeIgniter)框架中的增删改查操作
2014/06/10 PHP
php使用标签替换的方式生成静态页面
2015/05/21 PHP
PHP正则表达式之捕获组与非捕获组
2015/11/06 PHP
php项目开发中用到的快速排序算法分析
2016/06/25 PHP
PHP 接入微信扫码支付总结(总结篇)
2016/11/03 PHP
ExtJS GTGrid 简单用户管理
2009/07/01 Javascript
jValidate 基于jQuery的表单验证插件
2009/12/12 Javascript
Form表单按回车自动提交表单的实现方法
2016/11/18 Javascript
Angular5中调用第三方库及jQuery的添加的方法
2018/06/07 jQuery
新版小程序登录授权的方法
2018/12/12 Javascript
Angular Excel 导入与导出的实现代码
2019/04/17 Javascript
vuex state中的数组变化监听实例
2019/11/06 Javascript
[01:05:52]DOTA2-DPC中国联赛 正赛 Ehome vs Aster BO3 第一场 2月2日
2021/03/11 DOTA
python实现手机通讯录搜索功能
2018/02/22 Python
python 输入一个数n,求n个数求乘或求和的实例
2018/11/13 Python
解决Python一行输出不显示的问题
2018/12/03 Python
Python split() 函数拆分字符串将字符串转化为列的方法
2019/07/16 Python
详解Python 中的容器 collections
2020/08/17 Python
Python结合百度语音识别实现实时翻译软件的实现
2021/01/18 Python
css3学习系列之移动属性详解
2017/07/04 HTML / CSS
canvas画图被放大且模糊的解决方法
2020/08/11 HTML / CSS
Html5之webcoekt播放JPEG图片流
2020/09/22 HTML / CSS
Volcom法国官网:美国冲浪滑板品牌
2017/05/25 全球购物
英国街头品牌:Bee Inspired Clothing
2018/02/12 全球购物
法国大使拉杆箱官网:DELSEY Paris
2018/03/20 全球购物
澳大利亚婴儿喂养品牌:Cherub Baby
2018/11/01 全球购物
美国最大最全的亚洲购物网站:美国亚米网(Yamibuy)
2020/05/05 全球购物
送货司机岗位职责
2013/12/11 职场文书
放飞梦想演讲稿
2014/05/05 职场文书
大学生军训自我鉴定范文
2014/09/18 职场文书
大专毕业生自我鉴定范文(2篇)
2014/09/27 职场文书
代办社保委托书范文
2014/10/06 职场文书
朋友聚会开场白
2015/06/01 职场文书