大家在平時的開發過程中估計不會經常碰到需要主動取消一個 Fetch 請求的需求,所以一部分同學可能對這一塊知識不是很了解。沒有關系,看完這篇文章你就能夠掌握關于如何終止一個 Fetch 請求或者一個 Promise 的全部技能了。那我們趕快開始吧~
這篇文章比較長,大家現在沒時間瀏覽的可以先收藏起來,以后慢慢看。如果覺得這篇文章不錯的話,也可以幫忙點個贊。
使用 AbortController 終止 Fetch 請求
在 之前,我們請求后端的資源使用的方式是通過 這個構造函數,創建一個 對象,然后通過這個 對象進行請求的發送以及接收。
這個 上也存在一個 方法用來進行請求的終止操作。但是需要注意的是,這個 的執行過程是比較模糊的。我們不清楚 在什么時候可以不進行或終止對應的網絡請求,又或者如果在調用 方法和獲取到請求的資源之間存在競爭條件的時候會發生什么。我們可以通過簡單的代碼來實踐一下:
通過添加一個延時,然后取消掉對應的請求;在控制臺可以看到,有時請求已經獲取到結果了,但是卻沒有打印出對應的結果;有時請求沒有獲取到對應的結果,但是查看對應的網絡的狀態卻是成功的。所以這里面有很多的不確定性,跟我們的感覺是比較模糊的。
等到 出來的時候,大家就在討論關于如何正確,清楚地取消一個 請求。最早的討論可以看這里 Aborting a fetch #27 ,那已經是7年前(2015年)的事情了,可以看到當時的討論還是比較激烈的。大家感興趣的話可以看看當時大家都主要關注的是哪些特性。
最終,新的規范 出來了,通過 和 我們可以方便,快捷,清楚地終止一個 請求。要注意的是,這個規范是一個 DOM 層面的規范,不是 JAVAScript 語言層面的規范?,F在絕大多數的瀏覽器環境和新版本的 Node.js 環境也都支持這個特性了。關于 的兼容性,大家可以參考這里 AbortController#browser_compatibility
下面文章中的代碼例子基本上都可以直接復制粘貼到控制臺運行的,所以感興趣的同學閱讀到對應的部分可以直接打開瀏覽器的控制臺去運行一下,然后看看對應的結果。加深一下自己對相關知識點的記憶。
終止正在進行中的單個請求
我們先通過一段代碼來給大家展示一下如何實現這個功能
大家感興趣的話也可以把上面的代碼復制粘貼到瀏覽器的控制臺運行一下,上面代碼的運行結果如下所示:
可以看到控制臺的 Console 的輸出是:DOMException: The user aborted a request.對應的
這說明我們剛才發送的請求被終止取消掉了。能夠在一些特定的情況下主動地取消相關的請求對我們應用來說是很重要的,這能夠減少我們用戶的流量使用以及我們應用的內存使用。
AbortController 的深入剖析
接下來我們來講解一下上面的代碼,第一行通過 創建了一個 類型的實例 ,這個實例上有一個 方法和一個 類型的 實例。然后我們通過 方法去請求一個資源路徑,傳遞給 的選項把 的 對象傳遞進去。
方法如果獲取到了資源就會把資源打印到控制臺,如果網絡發生了問題,就會捕獲異常,然后把異常打印到控制臺。最后,通過一個 延時,調用 的 方法終止 請求 。
的 選項允許我們傳遞一個 對象; 的內部會監測這個對象的狀態,如果這個對象的狀態從未終止的狀態變為終止的狀態的話,并且 請求還在進行中的話, 請求就會立即失敗。其對應的 的狀態就會變為 。
如何改變 的狀態呢?我們可以通過調用 的 方法去改變 的狀態。一旦我們調用了 那么與之關聯的 的狀態會立刻從起始狀態(非終止狀態)轉變為終止狀態。
我們上面只是簡單地使用了 對象,這個對象是 類的實例,對于 我們下面會做深入的講解,這里暫時只需要知道 可以作為一個信號對象傳遞給 方法,可以用來終止 的繼續進行。另外,在不同的瀏覽器中打印的結果可能略有不同,這個跟不同瀏覽器的內部實現有關系。比如在 Firefox 中的結果如下:
在 Safari 中的結果如下:
當然如果我們沒有終止 請求的話,控制臺的打印將會是:
另外大家如果需要一些模擬的數據接口的話可以試試 JSONPlaceholder ,還是很方便使用的。
批量取消多個 fetch 請求
值得注意的是,我們的 對象可以同時傳遞給多個請求,在需要的情況下可以同時取消多個請求;我們來看看如何進行這樣的操作。代碼如下所示:
運行代碼后可以在控制臺看到如下結果:
如果我們需要同時對多個請求進行終止操作的的話,使用上面這種方式非常簡單方便。
如果我們想自定義終止請求的原因的話,可以直接在 方法里傳遞我們想要的原因,這個參數可以是任何 類型的值。傳遞的終止的原因會被 接收到,然后放在它的 屬性中。這個我們下面會講到。
的相關屬性和方法
詳細介紹 AbortSignal
AbortSignal 的屬性和方法
接口繼承自 EventTarget ,所以 對應的屬性和方法, 都繼承下來了。當然還有一些自己特有的方法和屬性,我們下面會一一講解到的。需要注意的是, 部分屬性有兼容性問題,具體的兼容性大家可以參考這里 AbortSignal#browser_compatibility 。
靜態方法 abort 和 timeout
這兩個方法是 類上的靜態方法,用來創造 實例。其中 用來創造一個已經被終止的信號對象。我們來看下面的例子:
運行代碼,控制臺的輸出結果如下:
對應的請求甚至都沒有發送出去
我們也可以給 方法傳遞終止的原因,比如是一個對象:
那么輸出的結果就如下圖所示:
的 屬性就變成了我們自定義的值。
同樣的,大家看到 應該很容易想到是創造一個多少毫秒后會被終止的 對象。代碼如下:
代碼的運行結果如下:
可以看到我們打印了兩次 ,第一次是立即打印的,第二次是等到請求被終止后打印的??梢钥吹降谝淮蛴〉臅r候, 的狀態還是沒有被終止的狀態。當請求被終止后,第二次打印的結果表明 這個時候已經被終止了,并且 屬性的值表明了這次請求被終止是因為超時的原因。
屬性 aborted 和 reason
實例有兩個屬性;一個是 表示當前信號對象的狀態是否是終止的狀態, 是起始狀態,表示信號沒有被終止, 表示信號對象已經被終止了。
屬性可以是任何的 類型的值,如果我們在調用 方法的時候沒有傳遞終止信號的原因,那么就會使用默認的原因。
默認的原因有兩種,一種是通過 方法終止信號對象,并且沒有傳遞終止的原因,那么這個時候 的默認值就是:;如果是通過 方法終止信號對象,那么這個時候的默認原因就是:。如果我們主動傳遞了終止的原因,那么對應的 的值就是我們傳遞進去的值。
實例方法 throwIfAborted
這個方法通過名稱大家也能猜出來是什么作用,那就是當調用 的時候,如果這個時候 對象的狀態是終止的,那么就會拋出一個異常,異常的值就是對應 的 值??梢钥聪旅娴拇a例子:
運行后在控制臺的輸出如下:
可以看到直接拋出異常,這個時候我們可以通過 進行捕獲,然后再進行對應的邏輯處理。這個方法也是很有幫助的,我們在后面會講到。當我們實現一個自定義的可以主動取消的 的時候這個方法就很有用。
事件監聽 abort
對于 對象來說,它還可以監聽 事件,然后我們就可以在 被終止的時候做一些額外的操作。下面是事件監聽的簡單例子:
運行后在控制臺的輸出如下:
可以看到在 被終止的時候,我們之前添加的事件監聽函數就開始運行了。其中 表示的是接收到的事件對象,然后這個事件對象上的 和 表示的就是對應的 對象。
實現一個可以主動取消的 Promise
當我們對 以及 比較熟悉的時候,我們就可以很方便的構造出我們自定義的可以取消的 了。下面就是一個比較簡單的版本,大家可以看一下:
這次的代碼稍微多了一點,不過相信大家還是很容易就知道上面的代碼要表示的是什么意思。
首先我們自定義了 這個函數,然后函數接收一個非必傳的 對象;然后立即返回一個新構建的 ,這個 的內部我們添加了一些額外的處理。首先我們判斷了 是否存在,如果存在就調用它的 方法。因為有可能這個時候 的狀態已經是終止的狀態了,需要立即將 的狀態變更為 狀態。
如果此時 的狀態還沒有改變,那么我們可以給這個 添加一個事件監聽,一旦 的狀態改變,我們就需要立即去改變 的狀態。
當我們下面的 的時間設置為100毫秒的時候,上面的 總是拒絕的狀態,所以會看到控制臺的打印結果如下:
如果我們把這個時間修改為2000毫秒的話,那么控制臺輸出的結果可能是 ok 也可能是一個 not good 的異常捕獲。
有同學看到這里可能會說,好像不需要 也可以實現主動取消的 ,我可以使用一個普通的 結合 也可以實現類似的效果。
當然我們也可以這樣做,但是一般情況下我們的異步操作是包含網絡請求的,如果網絡請求使用的是 方法的話,那么就必須使用 類型的實例 進行信號的傳遞;因為 方法內部會根據 的狀態來判斷到底需不需要終止正在進行的請求。
的相關屬性和方法:
開發中其他場景的使用舉例
取消事件監聽的一種便捷方法
一般情況下,如果我們對文檔中的某個 DOM 元素添加了事件監聽,那么當這個元素被銷毀或者移除的時候,也需要相應的把對應的事件監聽函數移除掉,不然很容易出現內存泄漏的問題。所以一般情況下我們會按照下面的方式添加并且移除相關的事件監聽函數。
這種方式是最通用的方式,但是這種方式需要我們保留對應事件監聽函數的引用,比如上面的 。一旦我們丟失了這個引用,那么后面就沒有辦法取消這個事件監聽了。
另外,有些應用場景需要你給某個元素添加很多事件處理函數,取消的時候就需要一個一個去取消,很不方便。這個時候我們的 就可以派上用場了,我們可以使用 來同時取消很多事件的事件監聽函數。就像我們同時取消很多個 請求一樣。代碼如下:
這樣的處理方式是不是就很方便,也非常的清楚明了。
的第三個參數可以是一個 對象,這個對象可以讓我們傳遞一個 對象用來作為事件取消的信號對象。就像上面我們使用 對象來取消 請求那樣。
從上面的兼容性來說,這個屬性的兼容性還是可以的;目前只有 Opera Android 和 Node.js 暫時還不支持,如果想要使用這個新的屬性,需要針對這兩個平臺和運行環境做一下兼容處理就好了。
一種值得借鑒的處理復雜業務邏輯的方法
我們有時開發中會遇到一些比較復雜的處理操作,比如你要先通過好幾個接口獲取數據,然后組裝數據;然后再把這些數據異步地繪制渲染到頁面上。如果用戶主動取消了這個操作或者因為超時了,我們要主動取消這些操作。對于這種場景,使用 配合 也有不錯的效果,下面舉一個簡單的例子:
上面是一個簡化的例子,用來表示這種復雜的操作;我們可以看到,如果用戶主動取消或者因為超時取消操作;我們上面的代碼邏輯可以很方便的處理這種情況。也不會因為少處理了一些操作而導致可能發生的內存泄漏。
一旦我們想重新開始這個操作,我們只需要再次調用 并且傳遞一個新的 對象就可以了。這樣處理后,重新開始和取消的邏輯就非常清楚了。如果大家在自己的項目中有類似的這種操作,不妨可以試試這種處理方法。
在 Node.js 中的使用
我們不僅可以在瀏覽器環境中使用 和 ,還可以在 環境中使用這兩個功能。對于 中的 ,,, 和 以及新版本支持的 都可以使用 來進行操作的取消。下面我們來舉一個簡單的例子,關于讀取文件的操作:
運行代碼可以看到終端的輸出如下:
經常使用 進行業務開發的同學可以嘗試使用這個新的特性,應該對開發會很有幫助的。
反饋和建議
這篇文章到這里就算結束啦,不知道有多少同學堅持讀完了這篇文章;希望讀完的同學都能夠掌握好這篇文章中講解的知識。如果這篇文章幫到了你,或者打開了你的新世界;歡迎點贊轉發。
參考的相關網址
MDN - AbortController
DOM Living Standard - Interface AbortController
How to Cancel Promise with AbortController
Using AbortController as an Alternative for Removing Event Listeners
The complete guide to AbortController in Node.js
AbortController is your friend
Fetch: Abort
Abortable fetch
EventTarget.addEventListener()
MDN - fetch()
- EOF -