书到用时方恨少,事非经过不知难。(陆游)
在这里插入图片描述

应用场景

取消请求在前端有时候会用到,以下是两个工作中可能会用到的场景

  1. tab切换时刷新某个列表数据,如果他们共用一个变量存储数据列表,当请求有延时,可能会导致两个tab数据错乱;
  2. 导出文件或下载文件时,中途取消 。

如何取消请求

给构造函数 CancelToken 传递一个 executor 函数作为参数。这种方法的好处是,可以用同一个 cancel token 来取消多个请求

const CancelToken = axios.CancelToken;
let cancel;
axios.get('/user/12345', {
  cancelToken: new CancelToken(function executor(c) {
    // 参数 c 也是个函数
    cancel = c;
  })
});
// 取消请求,参数用法同上
cancel();

项目中用法示

日常项目会对axios 做二次封装。token传递,请求拦截,响应拦截。参数封装

import axios from 'axios';
import {ElMessage} from 'element-plus';
import {removeToken, getToken} from '/@/utils/storage';
import {toQueryString, isObject} from '/@/utils/util';


// 配置新建一个 axios 实例
const service = axios.create({
  baseURL: import.meta.env.VITE_APP_BASE_API as any,
  timeout: 50000,
  headers: {'Content-Type': 'application/json'},
});


// 添加请求拦截器
service.interceptors.request.use(
  (config) => {=
    // 在发送请求之前做些什么 token
    if (getToken()) {
      if (config.headers) {
        config.headers.Authorization = `Bearer ${getToken()}`;
      } else {
        config.headers = {
          Authorization: `Bearer ${getToken()}`
        }
      }
    }
    return config;
  },
  (error) => {
    // 对请求错误做些什么
    return Promise.reject(error);
  }
);
const finalReturn = (response: any) => response.config.fullResponse ? response : response.data

// 添加响应拦截器
service.interceptors.response.use(
  async (response) => {
    // 对响应数据做点什么
    const res = response.data;
    const state = Number(res.state) || 200

    if (state === 1000) {
      return finalReturn(response)
    } else if (state === 700 || state === 2000) {
      removeToken()
      window.location.reload()
    } else {
      ElMessage({
        message: res.message || res.msg || 'Error',
        type: 'error',
        duration: 5 * 1000
      })

      const error = {
        '错误URL': response.config.url,
        '状态码': response.request.status,
        '返回数据': response.request.response
      }

      return new Error(JSON.stringify(error, null, '\t'))
    }
    return res;
  },
  (error) => {
    ElMessage({
      message: error.message,
      type: 'error',
      duration: 5 * 1000
    })
    return Promise.reject(error)
  }
);

对于某一个请求添加取消的功能,要在调用api时,加上cancelToken选项,使用时的示例:

// api.js
import request from 'request'
export function getUsers(page, options) {
  return request({
    url: 'api/users',
    params: {
      page
    },
    ...options
  })
}

// User.vue
import { CancelToken, isCancel } from 'axios'
import { getUsers } from 'api'

...

cancel: null

...

toCancel() {
  this.cancel('取消请求')
}

getUsers(1,
  {
    cancelToken:  new CancelToken(c => (this.cancel = c))
  }
)
.then(...)
.catch(err => {
  if (isCancel) {
    console.log(err.message)
  } else {
    ...
  }
})

以上,我们就可以顺顺利利地使用封装过的axios,取消某一个请求了。其原理无非就是把cancelToken的配置项,在调用api时加上,然后就可以在业务代码取消特定请求了。

用数组保存每个需要取消的cancel token,然后逐一调用数组里的每一项即可

// User.vue
import { CancelToken, isCancel } from 'axios'
import { getUsers } from 'api'

...

cancel: []

...

toCancel() {
  while (this.cancel.length > 0) {
    this.cancel.pop()('取消请求')
  }
}

getUser1(1,
  {
    cancelToken:  new CancelToken(c1 => (this.cancel.push(c1)))
  }
)

getUser2(2,
 {
  cancelTokem: new CancleTokem(c2 => (this.cancel.push(c2)))
 }
)

切换路由时,取消请求

大概思路就是在请求拦截器里,统一加个token,并设置全局变量source控制一个cancel token,在路由变化时调用cancel方法。

http.interceptors.request.use(config => {
    config.cancelToken = store.source.token
    return config
}, err => {
    return Promise.reject(err)
})

router.beforeEach((to, from, next) => {
    const CancelToken = axios.CancelToken
    store.source.cancel && store.source.cancel()
    store.source = CancelToken.source()
    next()
})

// 全局变量
store = {
    source: {
        token: null,
        cancel: null
  }
}

axios 从 v0.22.0 开始,支持以 fetch API 方式—— AbortController 取消请求

主要代码:

const controller = new AbortController();

axios.get('/foo/bar', {
   signal: controller.signal
}).then(function(response) {
   //...
});
// 取消请求
controller.abort()
// request.js
// 添加请求拦截器
service.interceptors.request.use(
  (config) => {
    // 创建AbortController 实例
    const controller = new AbortController();
    useRoutesListStore().setController(controller)
    // config.headers.common['Authorization'] = `Bearer dfb1ed07-3318-4549-b18b-9791161ef62b`
    // 在发送请求之前做些什么 token
    if (getToken()) {
      if (config.headers) {
        config.headers.Authorization = `Bearer ${getToken()}`;
      } else {
        config.headers = {
          Authorization: `Bearer ${getToken()}`
        }
      }
      // config.headers.common['Authorization'] = `Bearer ${Session.get('token')}`
    }
    // config中配置signal
    config.signal = controller.signal
    return config;
  },
  (error) => {
    // 对请求错误做些什么
    return Promise.reject(error);
  }
);

需要的页面在onMounted中引用下

// index.vue
    onUnmounted(() => {
     // 取消上一个路由的请求
      const routeListStore = useRoutesListStore()
      routeListStore.$state.requestController?.abort()
    })
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐