MUKI AI Summary
Vue 使用 axios 搭配 AbortController 來取消頁面離開時的 API 請求,提升換頁速度並節省資源。使用前需確認 axios 版本為 v0.22.0 以上,因為新版已改用 AbortController。Vue2 可在 beforeDestroy 中使用 abort,Vue3 則在 onBeforeUnmount 中使用。每個請求可有自己的 AbortController 實體,以便取消指定請求。升級 axios 時需注意相容性問題,建議逐步升級並尋求有經驗的工程師協助。
在 axios 攔截器中設定 AbortController 需謹慎,以免導致 API 錯誤。若 config 需同時傳 signal 和其他資料,可調整參數傳入方式。在多 API 請求情境下,為每個請求建立獨立的 AbortController 實體,確保能取消指定請求。...
源起
我們公司因為有從硬體上傳監測資料的服務,所以資料量非常的大,導致在打 API 時,前後端 + 資料庫的處理要很久,雖然有將不同的硬體 sensor 拆成數隻 API 分開打,但每一隻 API 平均還是要個十秒以上。
因此如果使用者在 A 頁面,一次打了 5 隻 API,然後反悔了要前往 B 頁面,但正常邏輯來說,我們要等前面的 5 隻 API 回傳 success
/ fail
才會繼續下一個 API,這樣就導致頁面要 loading 非常久。那對使用者來說,他會很疑惑的是,「為什麼我點了 B 頁面,卻一直停留在 A 頁面,要等好久好久才會前往 B 頁面?」
因此我在想,有沒有一個方法是,只要我前往 B 頁面,就取消所有的 api 請求,從 pending
改成 canceled
,這樣換頁的速度會快很多,也不會浪費資源。
上網查了一下,還真的有 XD
先確認你的 axios 版本
以前可以用 cancelToken.cancel
取消 api 請求,但 axios
新版全面改用 abortController
,所以記得要先確認你的 axios
版本,是否為 v0.22.0 以上?
Starting from
axios githubv0.22.0
Axios supports AbortController to cancel requests in fetch API way
這是我踩的第一個雷,我改了半天才發現該專案的 axios
是 v0.19
(好蠢)
如果你的專案 axios
版本低於 v0.22.0
,可以試著往上升,但如果版本號差太多,也不要一下子升到最新版,不然有些 module 可能不相容。
# 先移除舊的 axios $ npm uninstall axios # 再安裝特定的版本號 $ npm install axios@0.22.0
如果不確定升級後會不會出錯,建議找個有經驗的工程師幫忙看下,通常 axios
的相依性套件不多,但如果裝了很多套件,還是要注意這部分唷。
基本的使用方法
axios
的官方文件已經有很清楚的範例,有興趣的朋友可以直接參考:
▼ AbortController / 官方範例
const controller = new AbortController(); axios.get('/foo/bar', { signal: controller.signal }).then(function(response) { //... }); // cancel the request controller.abort()
▼ 以下分享 VUE2 的寫法
data() { return { abortController: new AbortController() } } beforeDestroy() { this.abortController.abort() }, methods: { getData(){ axios.post(url, data, { signal: this.abortController.signal }).then(res => ()) } }
用 beforeDestroy()
就可以在離開頁面的時候,取消所有 api 請求囉。
▼ 以及 VUE3 的寫法
import { onBeforeUnmount } from 'vue' export default { setup () { const controller = new AbortController() } onBeforeUnmount(() => { controller.abort() }) const getData = () => { $http.post( url, data, { signal: controller.signal }).then((res) => {}) }
▼ 換頁後會取消所有的 api 請求
用 axios 攔截器的使用方法
我目前還不太清楚,要怎麼在攔截器做到「離開頁面就取消所有 api 請求」🤔,但是如果要設定 AbortController()
,也是可以在 request 這邊統一處理:
// 語法來源: https://stackoverflow.com/a/72005814/2126434 axiosInstance.interceptors.request.use( function (config) { const controller = new AbortController(); const cfg = { ...config, signal: controller.signal, }; controller.abort('We gotta cancel this'); return cfg; }, function (error) { return Promise.reject(error); }, );
我有試著參考以上的程式碼,加在專案的攔截器裡,但有一些 api 會直接報錯,所以我覺得 controller.abort
需要再做一些調整,因此我目前傾向為個別處理。
特殊狀況:config 同時要送 signal 跟其他資料
我接手處理的這個專案,在打 API 的時候,並不是在 payload
傳入 data,也就是我們熟悉的格式:
// 並不是像這個範例,在 payload 傳入 data axios.post(url, data, { signal: this.abortController.signal }).then(res => ())
▼ 他是在 config
接我們的 data
我卡在這邊卡了超久 囧....。
因為這個專案不是我寫的,我是後來接手的,所以就先入為主地以為是 payload
的 data,導致我的 signal 放錯位置,沒有成功進入 config
😂。
如果你也有碰到類似的情況,可以參考我的作法
調整 api 傳入的參數
在這邊只傳入一個物件,就是 config
:
axios.post({ data: data, signal: this.abortController.signal }).then(res => {
接著調整 axios.interceptors
service.interceptors.request.use( config => { let cfg = {...config} if (config.data && config.data.data) { cfg = { ...config, data: config.data.data, signal: config.data.signal } } console.log(cfg) return cfg; } }
這樣就可以順利傳入 config.data
以及 config.signal
兩個資料囉!
取消指定的 request
(2022.08.31 新增)
最近碰到的新需求是,透過開關(show / off) 顯示多個產品的資料,每個產品都是一隻獨立的 API。
但因為 API 讀取時間較久,考慮到使用者可能不想看這產品了,或是讀取中途想要取消,希望能做到取消 API 的需求。點擊 A / B / C 會打 API,再點擊一次 A / B / C 會取消 API 請求 (變灰色)
我原本的寫法是共用一個 AbortController(),這樣就會導致使用者只能取消最後一隻 API request,無法取消指定的 request。
因此我希望能做到點了 A > B > C 之後,再點 A 就會取消 A 請求,點 B 取消 B 請求,而不是只取消最後一個 C。
在前端社團詢問後,了解到每個參數可以有自己的 AbortController()
實體,範例如下:
ps. 可以打開開發者工具調整網路速度為慢速 3G (slow 3G),不然網速太快很快就打完 API 了 😂
參考資料
附上我參考的幾個資料,還有我在 stack overflow 問的問題
- vue 在頁面離開時,丟棄所有未完成的請求 AbortController signal / CancelToken.cancel 取消請求
- Axios: how to cancel request inside request interceptor properly?
- AbortController is not working in ajax and vue.js / ask from mukiwu
- Facebook 前端社團 / ask from mukiwu