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

來源:互聯網
上載者:User

事情起因是這樣的,因為頁面上有多個相似的非同步請求動作,本著提高代碼可重用性的原則,我封裝了一個名為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秒,如下:

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

  瀏覽器的渲染(UI)線程和js線程是互斥的,在執行js耗時操作時,頁面渲染會被阻塞掉。當我們執行非同步ajax的時候沒有問題,但當設定為同步請求時,其他的動作(ajax函數後面的代碼,還有渲染線程)都會停止下來。即使我的DOM動作陳述式是在發起請求的前一句,這個同步請求也會“迅速”將UI線程阻塞,不給它執行的時間。這就是代碼失效的原因。

setTimeout解決阻塞問題
  既然明白了問題在哪裡,我們就來針對性想辦法。為了不讓同步ajax請求阻塞線程,我想到了setTimeout,把請求的代碼放到sestTimeout中,讓瀏覽器重啟一個線程來操作,不就解決問題了嗎?於是乎,My Code就變成了這樣:

 

 代碼如下 複製代碼
$('.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對象,提供的很方便的廣義非同步機制。詳情可參看阮一峰老師的這篇文章  於是我用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做了最佳化,事實到底是什麼,我暫未得知。有高人知道還請指點。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.