JavaScript, Vue

2022.08.04

vue 在離開頁面時,會取消所有的 api 請求,使用 axios 搭配 AbortController signal API

源起

我們公司因為有從硬體上傳監測資料的服務,所以資料量非常的大,導致在打 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 v0.22.0 Axios supports AbortController to cancel requests in fetch API way

axios github

這是我踩的第一個雷,我改了半天才發現該專案的 axiosv0.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 問的問題

歡迎給我點鼓勵,讓我知道你來過 :)

3
1
3
7
2
1
guest

0 則留言
Inline Feedbacks
View all comments
Copyright (C) MUKI space* / Reborn Theme All Rights Reserved.