axios攔截重複請求(當請求未完成時取消後面發起的相同請求)

背景

最近作的項目有一個奇葩問題,項目作完後交給甲方,因爲甲方數據庫比咱們模擬的庫在數據量上大好多(單表有幾個億的量)。直接致使接口請求的時間都在30s以上。。。前端

固然數據的優化只能交給後端的同事去作了。可是目前最主要的任務是提供一個能看的前端頁面給甲方驗收。ios

請求慢的問題暴露了前端的不少缺陷。在補充了加載提示等功能後,用戶大量的重複點擊產生的重複接口請求致使相同的數據在請求結束後蜂擁而來。數據庫

網上找了一些博主的方法(見文未連接),所有都是攔截了前面的請求。好比說發了一個請求a, 在 a 尚未完成時,又發了一個與 a 同樣的請求 a2 ,博主們的方法是取消了請求a只保留a2axios

以上方法在接口快的時候是不會有問題的。可是接口慢的時候,a接口都快要請求完成了,這時給它來了個取消。就有點不合理。segmentfault

因此我如今的作法是取消後來的a2,保留a後端

開始

爬一爬axios官方文檔傳關門,要取消一個正在發出或已發出可是尚未返回數據的請求,用到的是axios.CancelToken這個對像裏面的方法,以下:數組

  1. 第一種方法:使用 CancelToken.source 工廠方法建立 cancel token,像這樣:
const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios.get('/user/12345', {
  cancelToken: source.token
}).catch(function(thrown) {
  if (axios.isCancel(thrown)) {
    console.log('Request canceled', thrown.message);
  } else {
     // 處理錯誤
  }
});

axios.post('/user/12345', {
  name: 'new name'
}, {
  cancelToken: source.token
})

// 取消請求(message 參數是可選的)
source.cancel('Operation canceled by the user.');
  1. 第二種方法: 經過傳遞一個 executor 函數到 CancelToken 的構造函數來建立 cancel token:
const CancelToken = axios.CancelToken;
let cancel;

axios.get('/user/12345', {
  cancelToken: new CancelToken(function executor(c) {
    // executor 函數接收一個 cancel 函數做爲參數
    cancel = c;
  })
});

// cancel the request
cancel();

兩種方法的區別與應用

第一種方法是多個請求共用一個token,適合在某一時間同時取消全部請求,第二種方法每一個請求有獨立的token,適合每一個請求分狀況是否進行單獨取消。函數

顯然本次需求要用第二種方法。post

實現

核心方法就是在請求時將請求信息存在一個數組裏,而後在請求結速以後在這個數組中移除。下次再發請求時就在數組裏找是否存在相同的請求,若是存在,那麼取消本次請求。測試

參看axios文檔,在請求攔截響應攔截都會反回請求的原始數據。

// 添加請求攔截器
// config中有 url, data, params等信息
axios.interceptors.request.use(function (config) {
    return config;
});

// 添加響應攔截器
// 其中response.config與請求攔截的config是同樣的
axios.interceptors.response.use(function (response) {
    return response;
});

封裝方法

const CancelToken = axios.CancelToken
let requestQueue = []

// 請求攔截調用
function handleRequest({ config }) {
  // 提取四個參數用於區分相同的請求
  const { url, method, data = {}, params = {} } =  config;
  const jData = JSON.stringify(data),jParams = JSON.stringify(params)
  
  const panding = requestQueue.filter(item => {
    return item.url === url && item.method === method && item.data === jData && item.params === jParams
  })
  if(panding.length){
    // 這裏是重點,實例化CancelToken時,對參數 c 進行當即調用,便可當即取消當前請求
    config.cancelToken = new CancelToken(c => c(`重複的請求被主動攔截: ${url} + ${jData} + ${jParams}`))
  }else{
    // 若是請求不存在,將數據轉爲JSON字符串格式,後面比較好對比
    requestQueue.push({
      url,
      data: jData,
      params: jParams,
      method,
    })
  }
}

// 響應攔截調用
function handleResponse({ config }) {
  const { url, data = JSON.stringify({}), params = JSON.stringify({}) } = config
  let reqQueue = requestQueue.filter(item => {
    return item.url !== url && item.data !== data && item.params !== params
  })
  requestQueue = reqQueue
}

在請求封裝調用

// 請求攔截
axios.interceptors.request.use(function (config) {
    handleRequest({ config })
    return config;
});

// 響應攔截器
axios.interceptors.response.use(function (response) {
    handleResponse({ config: response.config })
    return response;
});

試運行結果
NbiZz8.png

總結一下

  1. 以上方法用了 axios 的CancelToken方法,其實文檔沒有詳細說明清楚何時能夠調用這個方法,致使不少開發者覺得只能在panding狀太下才可使用,通過個人測試,在請求攔截中就能夠調用cancel。
  2. 上述方法存在的問題: 用了JSON.stringify處理對像,JSON的方法用在對像上是會出現鍵值排列錯亂的狀況,能夠調用sort()方法來先排序。因爲我在項目中的接口細分比較多,請求參數都不是不少,無視了這個缺陷。
  3. 在請求攔截中不return config,不就不會發請求了嗎?經測式,若是在請求攔截中不return 或i return config,會在axios拋出一個報錯。
  4. 個別接口雖然已被攔截,可是沒有在控制檯中打印Cancel{message: 'url'}這個信息。找不到緣由。

本次更改在寫博文時沒有通過生產檢驗,歡迎你們幫我找找bug

參考:

axios 的二次封裝(攔截重複請求、異常統一處理)

axios中取消請求及阻止重複請求的方法

相關文章
相關標籤/搜索