咱們開發web頁面時候,也許會遇到和異步請求取消相關的問題。jquery
如:在一個請求發送以後,用戶作了一個取消指令,爲了節省資源,咱們須要把已經被用戶取消的請求終止掉;或者是一個頁面正在用ajax請求後臺,忽然頁面發生了跳轉,而咱們未完成的ajax莫名其妙地走進了error裏面了。git
爲了解決這兩問題,咱們今天一塊兒看看和異步請求取消相關的那些事。github
當咱們建立一個XMLHttpRequest對象的時候,咱們就會發現兩個api——abort和onabort,這就是終止異步請求的方法與其響應事件。web
執行完abort以後,瀏覽器和被請求的服務器都會發生什麼呢?MDN的解釋很是的簡單,就是中斷已發送的請求。這個請求指的是http請求,而不是tcp鏈接,這樣就會出現一個問題,基於http請求原理,當一個請求從客戶端發出去以後,服務器端收到請求後,一個請求過程就結束了,這時就算是客戶端abort這個請求,服務器端仍會作出完整的響應,只是這個響應客戶端不會接收罷了。ajax
因此這個abort是僅給客戶端使用的,不能做爲供服務器端判斷請求是否繼續執行的依據。chrome
那麼被abort的請求對客戶端有哪些影響呢?咱們能夠作一個實驗。api
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。promise
那麼咱們能用這兩個參數做爲判斷請求被abort的依據嗎?首先可以讓status等於0的狀況太多了,如請求本地資源、網絡不可用、請求超時,這些均可以讓status被置0;readyState等於0可否做爲請求是否被abort了還很差說,須要進一步判斷,readyState等於0至關於請求未初始化,請求都已經send了readyState卻等於0,筆者認爲是能夠做爲abort的判斷依據的,可是沒法徹底證實。瀏覽器
有沒有更可靠的證實請求是否執行了abort方法呢?有,答案是使用onabort,onabort做爲abort的響應函數,是最直接有效的判斷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」咱們的異步請求。
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」。
若是僅僅是頁面跳轉的時候,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.js和ajax.js源碼),根據status是不是成功響應的http狀態碼,來肯定執行error仍是success方法。同時,jquery也沒有獲取瀏覽器的readyState的值,而是經過status是否爲0去計算本身的xhr.readyState,能夠說全部的響應全是靠status一個變量決定的,這就致使了咱們沒法區分瀏覽器取消事件仍是真正的錯誤的問題。
在jquery2.x中,再也不僅監聽onreadystatechange(具體能夠參考github的xhr.js和ajax.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等),不然用戶在使用你的頁面的時候常常會出現意想不到的彈框。
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的一個不一樣點。