详解jQuery同步Ajax带来的UI线程阻塞问题及解决办法


Posted in jQuery onAugust 09, 2017

俗话说不作死就不会死,今天作死了一回,写了一个比较二逼的函数,遇到了同步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对象,提供的很方便的广义异步机制。详情可参看这篇文章https://3water.com/article/54762.htm。

于是我用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="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:Firefox有做优化?

上述问题在chrome和IE9中测试结论一致。但是我在Firefox中测试时,同步ajax并未阻塞掉UI线程,也就是说这个问题根本不存在。我用其他代码做了测试,在Firefox中js线程确实是会阻塞UI线程,这个没有疑问。那可能的一个猜测就是Firefox对同步ajax做了优化,事实到底是什么,我暂未得知。有高人知道还请指点。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

jQuery 相关文章推荐
jquery实现tab键进行选择后enter键触发click行为
Mar 29 jQuery
jQuery中map函数的两种方式
Apr 07 jQuery
推荐三款日期选择插件(My97DatePicker、jquery.datepicker、Mobiscroll)
Apr 21 jQuery
jQuery+Ajax实现用户名重名实时检测
Jun 01 jQuery
JQuery.dataTables表格插件添加跳转到指定页
Jun 09 jQuery
jQuery选择器_动力节点Java学院整理
Jul 05 jQuery
Jquery中.bind()、.live()、.delegate()和.on()之间的区别详解
Aug 01 jQuery
利用JQuery操作iframe父页面、子页面的元素和方法汇总
Sep 10 jQuery
js与jQuery实现的用户注册协议倒计时功能实例【三种方法】
Nov 09 jQuery
jQuery Migrate 插件用法实例详解
May 22 jQuery
jQuery实现的记住帐号密码功能完整示例
Aug 03 jQuery
jQuery实现查看图片功能
Dec 01 jQuery
快速掌握jquery分页插件jqPaginator的使用方法
Aug 09 #jQuery
jQuery Easyui Treegrid实现显示checkbox功能
Aug 08 #jQuery
jQuery EasyUI的TreeGrid查询功能实现方法
Aug 08 #jQuery
基于jQuery对象和DOM对象和字符串之间的转化实例
Aug 08 #jQuery
jquery+css实现简单的图片轮播效果
Aug 07 #jQuery
使用jQuery实现鼠标点击左右按钮滑动切换
Aug 04 #jQuery
jQuery选取所有复选框被选中的值并用Ajax异步提交数据的实例
Aug 04 #jQuery
You might like
PHP生成加减算法方式的验证码实例
2018/03/12 PHP
php抽象方法和普通方法的区别点总结
2019/10/13 PHP
js 鼠标拖动对象 可让任何div实现拖动效果
2009/11/09 Javascript
eval与window.eval的差别分析
2011/03/17 Javascript
JavaScript中实现依赖注入的思路分享
2015/01/15 Javascript
JavaScript中的函数嵌套使用
2015/06/04 Javascript
Bootstrap每天必学之模态框(Modal)插件
2016/04/26 Javascript
老生常谈遮罩层 滚动条的问题
2016/04/29 Javascript
详解angularJs指令的3种绑定策略
2017/04/13 Javascript
Vue2 配置 Axios api 接口调用文件的方法
2017/11/13 Javascript
为vue-router懒加载时下载js的过程中添加loading提示避免无响应问题
2018/04/03 Javascript
vue如何将v-for中的表格导出来
2018/05/07 Javascript
uni-app实现点赞评论功能
2019/11/25 Javascript
Ant Design Vue table中列超长显示...并加提示语的实例
2020/10/31 Javascript
elementui实现预览图片组件二次封装
2020/12/29 Javascript
[02:37]2018DOTA2亚洲邀请赛赛前采访-EG篇
2018/04/03 DOTA
详解python中的json的基本使用方法
2016/12/21 Python
Python DataFrame 设置输出不显示index(索引)值的方法
2018/06/07 Python
解决python测试opencv时imread导致的错误问题
2019/01/26 Python
windows安装TensorFlow和Keras遇到的问题及其解决方法
2019/07/10 Python
解决Keras的自定义lambda层去reshape张量时model保存出错问题
2020/07/01 Python
解析python 类方法、对象方法、静态方法
2020/08/15 Python
Pytorch之Tensor和Numpy之间的转换的实现方法
2020/09/03 Python
html5的画布canvas——画出简单的矩形、三角形实例代码
2013/06/09 HTML / CSS
全球最大最受欢迎的旅游社区:Tripadvisor
2017/11/03 全球购物
美国台面电器和厨具品牌:KitchenAid
2019/04/12 全球购物
HSRP的含义以及如何工作
2014/09/10 面试题
经典的班主任推荐信
2013/10/28 职场文书
一名女生的自荐信
2013/12/08 职场文书
自我评价的写作规则
2014/01/06 职场文书
小学优秀教育工作者事迹材料
2014/05/09 职场文书
我心目中的好老师活动方案
2014/08/19 职场文书
2016形势与政策学习心得体会
2016/01/12 职场文书
mysql timestamp比较查询遇到的坑及解决
2021/11/27 MySQL
Java中Quartz高可用定时任务快速入门
2022/04/03 Java/Android
Python必备技巧之函数的使用详解
2022/04/04 Python