zoukankan      html  css  js  c++  java
  • Jquery ajax 同步阻塞引起的UI线程阻塞的坑(loading图片显示不出来,layer.load延迟)

    今天想做一个点击地市用ajax重新获取数据刷新页面功能,因为ajax属于耗时操作,想在获取数据且加载页面时显示加载遮罩层,结果发现了ajax的好多坑。

        

        例如如上栗子,我想点击按钮让遮罩层显示,ajax加载完毕后遮罩层消失。因为我想让loadChart()在赋值操作后执行,但如果async设为true时,往往会先执行loadChart(),之后才会赋值,所以我只能将ajax设为同步。但同步后无论我怎么点按钮,遮罩层都不会出来。将显示遮罩放到beforeSend中也没用。用layer.load()也一样出不来。

        原因就是ajax的async设置为true时,ajax会委托浏览器另起一个线程,此线程与js线程和ui线程不冲突,只是在执行完成后再插入js事件环。而ajax的async设置为false时并没有启动单独的线程,还是在js主线程中执行,所以会与浏览器的渲染(UI)线程和js线程是互斥的,在执行js耗时操作时,页面渲染会被阻塞掉。当我们执行异步ajax的时候没有问题,但当设置为同步请求时,其他的动作(ajax函数后面的代码,还有渲染线程)都会停止下来。即使我的DOM操作语句是在发起请求的前一句,这个同步请求也会“迅速”将UI线程阻塞,不给它执行的时间。这就是代码失效的原因。

        同样在例如alert()中也会有这个坑。

        

        上图中页面时先显示helloWorld呢,还是先弹出1呢。结果是先弹出1,确定后才显示helloWorld;因为alert也会阻塞UI线程,而且会抢在ui线程(dom操作)执行前进行(并不影响例如console.log()出现的顺序)

        

        比如这样时console.log正常出现在alert()之前。

        回归原话题。这是我想到可能用setTimeout()让ajax一边执行去,或许不会阻塞线程。但还是错了

        JavaScript是单线程执行的,无法同时执行多段代码。当某一段代码正在执行的时候,所有后续的任务都必须等待,形成一个队列。一旦当前任务执行完毕,再从队列中取出下一个任务,这也常被称为 “阻塞式执行”。所以一次鼠标点击,或是计时器到达时间点,或是Ajax请求完成触发了回调函数,这些事件处理程序或回调函数都不会立即运行,而是立即排队,一旦线程有空闲就执行。假如当前 JavaScript线程正在执行一段很耗时的代码,此时发生了一次鼠标点击,那么事件处理程序就被阻塞,用户也无法立即看到反馈,事件处理程序会被放入任务队列,直到前面的代码结束以后才会开始执行。如果代码中设定了一个 setTimeout,那么浏览器便会在合适的时间,将代码插入任务队列,如果这个时间设为 0,就代表立即插入队列,但不是立即执行,仍然要等待前面代码执行完毕。所以 setTimeout 并不能保证执行的时间,是否及时执行取决于 JavaScript 线程是拥挤还是空闲。

        由上可知setTimeout并不是异步的,而是将其操作插入到js线程中,排队执行,造成异步的假象,这时setTimeout立即将ajax排到js线程中,仍然会造成ui阻塞,遮罩层还是出不来。

          查了半天,我发现以前没注意的东西,叫deferred对象。

        具体内容可以看大牛阮一峰老师博客:

        http://www.ruanyifeng.com/blog/2011/08/a_detailed_explanation_of_jquery_deferred_object.html 

      

    <script>
        var data;
        function toGetData() {
            var defer = $.Deferred();
            $.ajax({
                url: 'xxx', 
                type: "post", // 请求类型
                data: {
                }, 
                dataType: 'json', 
                async: true, // 是否异步
                success: function (ret) {
                    if (ret) {
                        data=ret;
                        defer.resolve(ret)
     
                    } else {
                        alert("无数据");
                    }
                } 
            });
            return defer;
        }
        $('button').click(function(){
            $(".shadow").show()
            $.when(toGetData()).done(function(ret){
                loadChart()
                $(".shodow").hide()   //所有的ajax的逻辑可以在这个地方进行处理
            });
        })
    </script>

     总之,想让ajax走完再加载页面,就要使用同步。但是只要同步,ajax就会阻塞ui线程,使得loading显示不出来。

    只有使用了deffer对象和$.when(),既可以ajax设为异步,保证了loading的正常显示,又可以保证在ajax走完再加载页面。因为$.when().done()会在deffer.resolve()之前的代码全部走完后才走done中的代码。

    我改成这样。由于ajax为同步时点击切换比较卡。能用异步最好还是用异步,用defferred对象后就可以把async换成true了。$.when()函数只接受defferred对象,所以我们在toGetData中需要先创建对象,再return就解决了。defer.resolve(ret)用于控制ajax何时结束,比如我执行完赋值操作结束ajax,进入.done()中的回调函数,它还可以把数据ret也带出来使用,这里我没有用到,这里执行完loadChart()操作后遮罩层消失。所以它能保证deffer.resolve之前的代码执行完再执行回到函数,async设为true也没任何影响。

         这样就完美解决了因为ajax阻塞线程导致loading层出不来的问题啦。

    转自:https://blog.csdn.net/lianzhang861/article/details/79426385

  • 相关阅读:
    loj#2333 「JOI 2017 Final」准高速电车
    loj#2332 「JOI 2017 Final」焚风现象
    loj#501 「LibreOJ β Round」ZQC 的树列
    loj#500 「LibreOJ β Round」ZQC 的拼图
    p2827 蚯蚓
    p5471 [NOI2019]弹跳
    p5304 [GXOI/GZOI2019]旅行者
    p2503 [HAOI2006]均分数据
    p4899 [IOI2018] werewolf 狼人
    loj#137 最小瓶颈路 加强版
  • 原文地址:https://www.cnblogs.com/yesu/p/9364090.html
Copyright © 2011-2022 走看看