axios.CancelToken(取消请求)的使用以及loading状态问题

296 阅读2分钟

一般使用到canceltoken应该是业务上的逻辑需要。例如这样一个场景:切换tab或者点解按钮查看不同的数据详情时,请求的是同一个接口,点击A后立即点击B,如果A的请求返回比B的慢,就会导致页面渲染的是A的数据。

以前也使用过canceltoken,这次再次使用,记录下

  1. 引用api
...
import axios from 'axios'
...
const instance = axios.create({
  ...
})

const { CancelToken } = axios

instance.interceptors.request.use(requestInterceptors.bind(this, CancelToken), requestError)

instance.interceptors.response.use(responseInterceptors, responseError)
  1. 新建cancelHttpReauest.js
// 请求连接的缓存
export let httpRequestList = []

/**
 * 取消方法
 * @param {Array,String} cancelPath 取消的url集合
 * @param {String} errorMsg 标识
 */
export const clearHttpRequestingList = (cancelPath = [], errorMsg = 'cancel') => {
  if (Array.isArray(cancelPath)) cancelPath = [...new Set(cancelPath)]
  if (httpRequestList.length > 0) {
    httpRequestList.forEach((item) => {
      const { url, cancel } = item
      // url集合
      if (Array.isArray(cancelPath) && cancelPath.includes(url)) {
        cancel(errorMsg)
        item.url = ''
      }
      // all 取消全部
      if (cancelPath === 'all') {
        cancel(errorMsg)
        item.url = ''
      }
    })
    httpRequestList = httpRequestList.filter((o) => o.url)
  }
}
  1. 在拦截器配置文件中引入cancelHttpReauest.js
...
import { httpRequestList, clearHttpRequestingList } from '../cancelHttpReauest'
...

// 请求拦截器
export function requestInterceptors(CancelToken, config) {
  const { url, cancelPath = [] } = config
  // 默认取消相同url且正在pending的请求
  if (typeof cancelPath !== 'string' && Array.isArray(cancelPath)) cancelPath.push(url)
  clearHttpRequestingList(cancelPath)
  config.cancelToken = new CancelToken(function executor(cancel) {
    httpRequestList.push({ url, cancel }) // 存储cancle
  })
  
  ...
  
  return config
}
  1. 调用
// cancelPath不传就取消当前url且正在pending的请求
httpRequest(params,{cancelPath:[...]}).then()

发现问题:例如请求httpRequest,请求时页面需要一个加载遮挡loading,在使用cancelToken之前,loading是这样写的

fn() {
  const params = {
    ...
  }
  this.loading = true
  httpRequest(params).then(() => {
    ...
  }).finally(()=>{
      this.loading = false
  })
}

这样会导致第二次调用httpRequest时,this.loading = true会比第一次this.loading = false先执行。页面效果就是没有加载遮罩了。

loading封装到axios拦截器中。

封装后的代码

...
import { httpRequestList, clearHttpRequestingList } from '../cancelHttpReauest'
...

// 请求拦截器
export function requestInterceptors(CancelToken, config) {
  const { url, cancelPath = [], loading } = config
  if (typeof cancelPath !== 'string' && Array.isArray(cancelPath)) cancelPath.push(url)
  clearHttpRequestingList(cancelPathWithCurrentPath)
  config.cancelToken = new CancelToken(function executor(cancel) {
    httpRequestList.push({ url, cancel }) // 存储cancle
  })
  if (loading) {
    const { vm, loadingKey } = loading
    vm?.[loadingKey] = true
  }
  ...
  
  return config
}

// 响应拦截器
export function responseInterceptors(response) {
  ...
  const { config } = response
  const { loading } = config
  if (loading) {
    const { vm, loadingKey } = loading
    vm?.[loadingKey] = false
  }
  ...
  return data
}

调用

// cancelPath不传就取消当前url且正在pending的请求
httpRequest(params,{cancelPath:[...], loading: { vm: this, loadingKey: 'loading' }}).then()