玩轉 AbortController 控制器
絕大部分情況,網路請求都是先請求先響應。但是某些情況下,由於未知的一些問題,可能會導致先請求的 api 後返回。最簡單的解決方案就是新增 loading 狀態,在所有請求都完成後才能進行下一次請求。
但不是所有的業務都可以採用這種方式。這時候開發者就需要對其進行處理以避免渲染錯誤資料。
使用“版本號”
我們可以使用版本號來決策業務處理以及資料渲染:
const invariant = (condition: boolean, errorMsg: string) => { if (condition) { throw new Error(errorMsg) } } let versionForXXXQuery = 0; const checkVersionForXXXQuery = (currentVersion: number) => { // 版本不匹配,就丟擲錯誤 invariant(currentVersion !== versionForXXXQuery, 'The current version is wrong') } const XXXQuery = async () => { // 此處只能使用 ++versionForXXXQuery 而不能使用 versionForXXXQuery++ // 否則版本永遠不對應 const queryVersion = ++versionForXXXQuery; // 業務請求 checkVersion(queryVersion) // 業務處理 // ?介面渲染 // 業務請求 checkVersion(queryVersion) // 業務處理 // ?介面渲染 }
如此,先請求的 api 後返回就會被錯誤中止執行,但最終渲染到介面上的只有最新版本的請求。但是該方案對業務的侵入性太強。雖然我們可以利用 class 和 AOP 來簡程式碼和邏輯。但對於開發來說依舊不友好。這時候我們可以使用 AbortController。
使用 AbortController
AbortController 取消之前請求
話不多說,先使用 AbortController 完成上面相同的功能。
let abortControllerForXXXQuery: AbortController | null = null const XXXQuery = async () => { // 當前有中止控制器,直接把上一次取消 if (abortControllerForXXXQuery) { abortControllerForXXXQuery.abort() } // 新建控制器 abortControllerForXXXQuery = new AbortController(); // 獲取訊號 const { signal } = abortControllerForXXXQuery const resA = await fetch('xxxA', { signal }); // 業務處理 // ?介面渲染 const resB = await fetch('xxxB', { signal }); // 業務處理 // ?介面渲染 }
我們可以看到:程式碼非常簡單,同時得到了效能增強,瀏覽器將提前停止獲取資料(注:伺服器依舊會處理多次請求,只能通過 loading 來降低伺服器壓力)。
AbortController 移除繫結事件
雖然程式碼很簡單,但是為什麼需要這樣新增一個 AbortController 類而不是直接通過新增 api 來進行中止網路請求操作呢?這樣不是增加了複雜度嗎?筆者開始也是這樣認為的。到後面才發現。AbortController 類雖然較為複雜了,但是它是通用的,因此 AbortController 可以被其他 Web 標準和 JavaScript 庫使用。
const controller = new AbortController() const { signal } = controller // 新增事件並傳遞 signal window.addEventListener('click', () => { console.log('can abort') }, { signal }) window.addEventListener('click', () => { console.log('click') }); // 開始請求並且新增 signal fetch('xxxA', { signal }) // 移除第一個 click 事件同時中止未完成的請求 controller.abort()
通用的 AbortController
既然它是通用的,那是不是也可以終止業務方法呢。答案是肯定的。先來看看 AbortController 到底為啥能夠通用呢?
AbortController 提供了一個訊號量 signal 和中止 abort 方法,通過這個訊號量可以獲取狀態以及繫結事件。
const controller = new AbortController(); // 獲取訊號量 const { signal } = controller; // 獲取當前是否已經執行過 abort,目前返回 false signal.aborted // 新增事件 signal.addEventListener('abort', () => { console.log('觸發 abort') }) // 新增事件 signal.addEventListener('abort', () => { console.log('觸發 abort2') }) // 中止 (不可以解構直接執行 abort,有 this 指向問題) // 控制檯列印 觸發 abort,觸發 abort2 controller.abort() // 當前是否已經執行過 abort,返回 ture signal.aborted // 控制檯無反應 controller.abort();
無疑,上述的事件添加了 abort 事件的監聽。綜上,筆者簡單封裝了一下 AbortController。Helper 類如下所示:
class AbortControllerHelper { private readonly signal: AbortSignal constructor(signal: AbortSignal) { this.signal = signal signal.addEventListener('abort', () => this.abort()) } /** * 執行呼叫方法,只需要 signal 狀態的話則無需在子類實現 */ abort = (): void => {} /** * 檢查當前是否可以執行 * @param useBoolean 是否使用布林值返回 * @returns */ checkCanExecution = (useBoolean: boolean = false): boolean => { const { aborted } = this.signal // 如果使用布林值,返回是否可以繼續執行 if (useBoolean) { return !aborted } // 直接丟擲異常 if (aborted) { throw new Error('abort has already triggered'); } return true } }
如此,開發者可以在實現類中放入 signal。然後通過一個 AbortController 中止多個乃至多種不同事件。
鼓勵一下
如果你覺得這篇文章不錯,希望可以給與我一些鼓勵,在我的 github 部落格下幫忙 star 一下。
參考資料
- 產品說明丨Android端使用MobPush快速整合方法
- 不要在 Python 中使用迴圈,這些方法其實更棒!
- 分享 6 個 Vue3 開發必備的 VSCode 外掛
- 微服務架構的外部 API 整合模式
- 前端該如何優雅地 Mock 資料
- 記一次springboot專案結合arthas排查ClassNotFoundException問題
- 使用Vue.js編寫命令列介面,前端開發CLI的利器
- 升級Spring Cloud最新版後,有個重要的元件被棄用了!
- 微服務架構的通訊設計模式
- 視覺化拖拽元件庫一些技術要點原理分析(四)
- 我做了一個線上白板(二)
- 3 款非常實用的 Node.js 版本管理工具
- 636. 函式的獨佔時間 : 簡單棧運用模擬題
- Flutter 的 6 個最有用的 VS Code擴充套件
- TCP 學習筆記(三) 可靠傳輸
- 一文讀懂微服務架構的分解設計
- Python中常用最神祕的函式! lambda 函式深度總結!
- 從-99打造Sentinel高可用叢集限流中介軟體
- 技術分享| 小程式實現音影片通話
- Birdseye 極其強大的Python除錯工具