jQuery Ajax同步參數致使瀏覽器假死怎麼辦

俗話說不做死就不會死,今天做死了一回,寫了一個比較二逼的函數,遇到了同步Ajax引發的UI線程阻塞問題,在此記錄一下。
 

事情原由是這樣的,由於頁面上有多個類似的異步請求動做,本着提升代碼可重用性的原則,我封裝了一個名爲getData的函數,它接收不一樣參數,只負責獲取數據,而後把數據return。基本的邏輯剝離出來是這樣的:php

 

 代碼以下 複製代碼
function getData1(){        var result;
        $.ajax({
            url : 'p.php',
            async : false,
            success: function(data){
                result = data;
            }
        });    return result;
}

  這裏的ajax不能用異步的,不然函數返回時,result還未賦值,會出錯。因此我加了async:false。看起來好像沒什麼問題。我調用這個函數能夠正常的獲得數據。html

 代碼以下 複製代碼
$('.btn1').click(function(){        var data = getData1();
        alert(data);
});

  接下來,要加另一個功能,因爲ajax請求有必定的耗時,因此我須要在發出請求前頁面有個loading效果,即顯示一張「正在加載」的gif圖片,想必你們也都見過。因此個人處理函數就變成了這樣:ajax

 代碼以下 複製代碼
$('.btn1').click(function(){
        $('.loadingicon').show();        var data = getData1();
        $('.loadingicon').hide();
        alert(data);
});

  請求以前顯示loading圖片,請求完成後把它隱藏。看起來也沒什麼問題。爲了看清效果,個人p.php代碼sleep了3秒,以下:chrome

<?phpsleep(3);echo ('aaaaaa');?>
  可是我運行的時候問題出現了,我點擊按鈕並未像預想的那樣出現這個loading圖片,頁面什麼反應也沒有。排除良久找到了緣由,就在async:false這裏。promise

  瀏覽器的渲染(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,瀏覽器會在一個已設的最小時間後執行。無論三七二十一先運行起來看看。async

  結果loading圖片顯示出來了,可是!!!圖片怎麼不動呢,我明明是一張動態gif圖。這個時候我很快就想到了,雖然同步請求延遲執行了,可是它執行期間仍是會把UI線程給阻塞。這個阻塞至關牛逼,連gif圖片都不動了,看起來像一張靜態圖片同樣。ide

  結論很明顯,setTimeout治標不治本,至關於把同步請求「稍稍」異步了一下,接下來仍是會進入同步的噩夢,阻塞線程。方案失敗。函數

是時候用Deferred了
  jQuery在1.5版本以後,引入了Deferred對象,提供的很方便的廣義異步機制。詳情可參看阮一峯老師的這篇文章  因而我用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作了優化,事實究竟是什麼,我暫未得知。有高人知道還請指點。

 

 

來自:http://www.cnblogs.com/haogj/p/4480772.html

相關文章
相關標籤/搜索