和瀏覽器異步請求取消相關的那些事


我們開發web頁面時候,也許會遇到和異步請求取消相關的問題。

如:在一個請求發送之后,用戶做了一個取消指令,為了節省資源,我們需要把已經被用戶取消的請求終止掉;或者是一個頁面正在用ajax請求后台,突然頁面發生了跳轉,而我們未完成的ajax莫名其妙地走進了error里面了。

為了解決這兩問題,我們今天一起看看和異步請求取消相關的那些事。

1.ajax的取消

當我們創建一個XMLHttpRequest對象的時候,我們就會發現兩個api——abort和onabort,這就是終止異步請求的方法與其響應事件。

執行完abort之后,瀏覽器和被請求的服務器都會發生什么呢?MDN的解釋非常的簡單,就是中斷已發送的請求。這個請求指的是http請求,而不是tcp連接,這樣就會出現一個問題,基於http請求原理,當一個請求從客戶端發出去之后,服務器端收到請求后,一個請求過程就結束了,這時就算是客戶端abort這個請求,服務器端仍會做出完整的響應,只是這個響應客戶端不會接收罷了。

所以這個abort是僅給客戶端使用的,不能作為供服務器端判斷請求是否繼續執行的依據

那么被abort的請求對客戶端有哪些影響呢?我們可以做一個實驗。

var xhr = new XMLHttpRequest();
xhr.open("GET","#");
xhr.send();
xhr.onload = function(){
    console.log("abort前");
    console.log(xhr.readyState);
    console.log(xhr.status);
    xhr.abort();
    console.log("abort后");
    console.log(xhr.readyState);
    console.log(xhr.status);
}

我們可以看到readyState和status在abort之后被重置回0。

那么我們能用這兩個參數作為判斷請求被abort的依據嗎?首先能夠讓status等於0的情況太多了,如請求本地資源、網絡不可用、請求超時,這些都可以讓status被置0;readyState等於0能否作為請求是否被abort了還不好說,需要進一步判斷,readyState等於0相當於請求未初始化,請求都已經send了readyState卻等於0,筆者認為是可以作為abort的判斷依據的,但是無法完全證明。

有沒有更可靠的證明請求是否執行了abort方法呢?有,答案是使用onabort,onabort作為abort的響應函數,是最直接有效的判斷abort手段。

2.頁面跳轉時候ajax會自動“abort”

 筆者從前認為abort離我很遠,但是在實際項目中,筆者發現頁面我開發的請求經常被abort。這個abort動作當然不是我發起的,也不是用戶發起的,他是瀏覽器自動發起的。筆者發現一個頁面跳轉的時候,瀏覽器會自動把所有響應未完成的請求執行“abort”,而響應已完成的請求則不會這樣。我們可以做一個實驗

//要在chrome或者webkit內核上運行
var xhr = new XMLHttpRequest();
//訪問一個不存在的地址 取保請求不會馬上響應
xhr.open("GET","http://aaa.bbbbbbbb.com");
xhr.send();
xhr.onabort = function(){
    console.log(xhr.readyState);
    console.log(xhr.status);
    alert("執行onabort");
};
setTimeout(function(){
    //模擬跳轉頁面
    location.href = "http://www.baidu.com"
},0);

結果網頁上彈出了一個alert,顯示着"執行onabort"。

再看控制台,我們會發現status不變還是0,而readyState卻是4,這也是瀏覽器發出的abort和手動執行abort最大不同。

以上測試僅在chrome上有效,ie、edge、火狐在頁面跳轉的時候,不會觸發未完成的請求的onabort事件,但是會觸發onreadystatechange事件。不管怎么講,當頁面發生跳轉的時候,瀏覽器可能會“abort”我們的異步請求。

3.jquery對abort的處理

jquery又是如何對abort封裝的呢?我們在使用$.ajax(包括眾多用$.ajax封裝的方法,如$.get、$.post)的時候,會返回一個xhr對象,這個基於$.deferred.promise封裝的jquery自己的對象,而不是原始的XMLHttpRequest或者ie的ActiveXObject對象。在這個對象中定義了如abort等方法,使得開發者可以手動abort一個ajax請求。

var xhr = $.ajax(url);
xhr.abort();

另外,jquery的超時也是通過setTimeout和abort實現的,所以當你使用jquery發出的請求超時的時候,實際上是被jquery把請求abort了。如何區分jquery的超時和手動abort呢?方法就是靠stutusText,對於timeout和abort兩個客戶端做出的響應,jquery會給stutusText設定固定的值,abort的時候,stutusText的值為“abort”,超時的時候stutusText值是“timeout”。

4.jquery與頁面跳轉的ajax“abort”

如果僅僅是頁面跳轉的時候,chrome瀏覽器會自動執行未完成的請求的abort方法,那筆者也不會專門寫一個章節去分析這個過程。因為筆者發現jquery的$.ajax這個方法中,未完成的ajax在頁面跳轉的時候,也會觸發error事件,而且你區分不出來是瀏覽器取消還是請求真的發生了error

大家可以運行如下代碼,在ie和chrome兩大瀏覽器下都會彈出的alert對話框。

var xhr = $.ajax({
    type:"get",
    url:"http://aaa.bbbbbbbb.com",
    error:function(){
        console.log(arguments);
        console.log(xhr.readyState);
        console.log(xhr.status);
        alert("執行onerror");
    }
});

setTimeout(function(){
    //模擬跳轉頁面
    location.href = "http://www.baidu.com"
},100);

頁面一跳轉就進error,而且status和readyState都是0,stutusText僅僅顯示一個“error”,jquery真是讓人佩(蛋)服(疼)的五體投地-_-||。

為什么會這樣呢?那么jquery又是如何處理onabort的呢?

筆者發現jquery1.x和2.x都會觸發這個現象,所以分別參考jquery1.x和2.x的源碼討論。

jquery1.x其實並沒有監聽onabort事件,而是統一監聽onreadystatechange(具體可以參考github的xhr.jsajax.js源碼),根據status是否是成功響應的http狀態碼,來確定執行error還是success方法。同時,jquery也沒有獲取瀏覽器的readyState的值,而是通過status是否為0去計算自己的xhr.readyState,可以說所有的響應全是靠status一個變量決定的,這就導致了我們無法區分瀏覽器取消事件還是真正的錯誤的問題。

在jquery2.x中,不再僅監聽onreadystatechange(具體可以參考github的xhr.jsajax.js源碼),而是對onload、onerror、onabort(不支持onabort事件的ie9還是監聽的onreadystatechange事件)全面監聽,並由這些響應事件的結果去確定究竟執行error還是success。這個處理看似更合理了,然而卻並沒有什么卵用,因為沒有監聽chrome瀏覽器的readyState實際值,仍然是通過status去計算readyState,所以仍然會觸發error事件,而且xhr.readyState的值還是0,stutusText還是僅僅顯示一個“error”。

說白了這就是jquery的一個bug,僅僅是根據status是否為0去判斷ajax結果,同時不返回瀏覽器真正的readyState值;當然我們也可以說是瀏覽器的bug,為什么chrome瀏覽器在頁面跳轉的時候要abort的請求呢。

不管怎么樣,筆者建議在使用jquery的$ajax做異步請求的時候,千萬不要在error回調中使用系統的模態框(如alert、confirm等),否則用戶在使用你的頁面的時候經常會出現意想不到的彈框。

5.fetch及promise如何取消與取消處理的

fetch作為ajax的升級版,越來越多的瀏覽器已經支持他了,那fetch又是如何取消異步請求的呢?答案是fetch暫時不能被取消...,因為沒有對應的api。

雖然不能取消,但是還是有替代品,當然這只是自欺欺人的做法,因為fetch根本沒有被真正取消,他的資源也沒有被釋放。

標題和fetch掛鈎,這讓筆者感覺有點大,因為筆者現在的項目中還不准備使用fetch。實際上筆者更想聊一聊我對abort和promise的看法,因為不管我們用不用fetch,將異步請求封裝成promise供后續處理都是我們現在開發的主流做法,那么如何用promise做abrot呢?

promise僅有兩個完成態,resolved和rejected。一個可以當做success處理,另一個可以當做error處理。那我們的abort的結果應該算在哪個里面呢?abort肯定不能將其看成success,但是abort是我們主動的動作(也可能是瀏覽器發出的被動abort),並不是發生真的發生了錯誤,將其列入error看起來也不合適。

其實所有非預想的結果都是異常,所以abort當然也是異常,既然是異常就應該當rejected對待。只是resolved的處理方案因為結果是預期中的,所有處理起來比較容易,但是rejected的處理往往很困難,因為各種異常的處理方法是應該不一樣的。比如abort這種異常,如果是因為用戶主動操作而產生的異常,那這種異常是不應該提示給用戶的,所以abort引起的異常應該包裝為特定的異常再進行rejected處理,以便在catch中,可以知道是什么異常,並作出對應的處理。

最后一點就是promise里resolved和rejected是不能切換的,所以一旦一個請求得到了響應,就不能再被abort了,而XMLHttpRequest對象是可以隨時執行abort的,這一點也是使用promise封裝異步請求和直接使用XMLHttpRequest的一個不同點。

參考:https://github.com/camsong/blog/issues/2


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM