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

  • 相关阅读:
    HDU 1009 FatMouse' Trade
    HDU 2602 (简单的01背包) Bone Collector
    LA 3902 Network
    HDU 4513 吉哥系列故事——完美队形II
    LA 4794 Sharing Chocolate
    POJ (Manacher) Palindrome
    HDU 3294 (Manacher) Girls' research
    HDU 3068 (Manacher) 最长回文
    Tyvj 1085 派对
    Tyvj 1030 乳草的入侵
  • 原文地址:https://www.cnblogs.com/yesu/p/9364090.html
Copyright © 2011-2022 走看看