axios 拦截器 接口封装

大纲链接 §

[toc]


1. axios 接口封装概述

安装略,引入axios

  • import axios from 'axios',后代码中略

处理加载页面效果

  • 在请求拦截器和响应式拦截器中根据记录的 正在发起的请求数 来处理
  • 请求拦截器 axios.interceptors.request.use()
  • 响应式拦截器 axios.interceptors.response.use()

二次封装axios的目的

  • 由于业务根据不同的功能模块,划分为不同的 url 前缀,所以需要二次封装 axios
  • 根据功能模块配置不同的 axios 配置

主动取消请求的场景

  • 搜索框 需要防止 用户输入每次一变化,就向服务端发送一次请求
  • 用户输入过快 前端用 debounce(如延时 200ms 发请求),连续输入间隔小于 200ms,之前的输入都不会发请求
  • 而当 后端接口很慢(如超 1s )之前的请求没有响应前,也有可能发出去多个请求
  • 先后两次请求 A -> BB 响应后发而先至,前面请求响应结果,覆盖后面请求响应结果的情况
  • 除了做 debounce,后发请求时,之前请求未响应,可以把前面的请求取消

全局配置 axios

  • 自定义请求头 axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
  • 将配置和调用解耦,将一些相关配置配置成常量后在引入使用
    • 创建env.ts设置并导出配置常量BASE_URLTIME_OUT
    • 基本请求公共路径 axios.defaults.baseURL = BASE_URL;
    • 超时时间 axios.defaults.timeout = TIME_OUT;
  • 或者使用环境配置文件.env.*
    • Vite 在一个特殊的 import.meta.env 对象上暴露环境变量
    • Vue-CLI 是基于webpack,它是在 process.env 上挂载的
    • 比如 .env.development 设置 VITE_APP_BASE_API='https://blog-server.hunger-valley.com'
    • 使用时,在js源代码文件中通过 import.meta.env.VITE_xxx来访问,类似vue/cli中的process.env.VUE_APP_BASE_URL

也可以通过创建实例,在创建时配置属性,以配置公共路径为例:

  • 调用 axios.create 方法创建实例 和 对该实例的拦截器 interceptors 方法进行重写,在此基础上添加取消重复请求的功能
  • const http = axios.create({ baseURL: import.meta.env.VITE_APP_BASE_URL, });
  • 然后使用实例
    • http.interceptors.request.use(/* ... */)
    • http.interceptors.request.use(/* ... */)
    • export default http;

参考


axios 接口封装

  • axios 取消重复请求 的封装
  • axios 响应错误后 重试请求 的封装
  • token 失效返回登录页面 的封装

参考


2. axios请求拦截器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
axios.interceptors.request.use(
  function (config) { // 在发送请求之前处理的逻辑
    return config;
  },
  function (error) { // 对请求错误处理的逻辑
    return Promise.reject(error);
  }
);

// 简记
axios.interceptors.request.use((config) => config, (err) => Promise.reject(err))

3. axios响应拦截器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
axios.interceptors.response.use(
  function (response) {
    // 2xx 范围内的状态码都会触发该逻辑。
    // 对响应数据处理的逻辑
    const { data } = response;
    return data;
  },
  function (error) {
    // 超出 2xx 范围的状态码都会触发该逻辑。
    // 对响应错误处理的逻辑
    return Promise.reject(error);
  }
);

// 简记
axios.interceptors.response.use((res) => res, (err) => Promise.reject(err))

4. 实现 axios 取消普通请求

对普通的 XMLHttpRequest 请求进行取消,用到了实例的 abort() 方法

1
2
3
4
5
const xhr = new XMLHttpRequest()
xhr.open('GET', '/abc')
xhr.send()
/* 取消当前请求 */ 
setTimeout(() => { xhr.abort() }, 300)

取消 axios 请求

  • 使用 axioscancelToken 方法
  • 获取实例 const source = axios.CancelToken.source()
  • config 中写入属性 cancelToken: source.token
  • 取消请求 source.cancel()
1
2
3
4
5
6
7
const source = axios.CancelToken.source()
axios.get('/user/12345', {
  cancelToken: source.token
}).then(res => {})

// cancel 方法里面的参数是可选的
source.cancel('Operation canceled by the user.')

5. axios 功能的二次封装

  • axios 取消重复请求 的封装
  • axios 响应错误后 重试请求 的封装
  • token 失效返回登录页面 的封装

通用代码,后略

1
2
3
4
5
import axios from "axios";
const http = axios.create({
  baseURL: import.meta.env.VUE_APP_BASE_URL,
});
// ...

axios 取消重复请求 的封装

实现思路

  • 针对多个重复请求发送
  • 先后紧接着发送了两个相同的请求,对前一个请求进行取消操作,真正发送出去的是后一个
  • 判断请求重复
    • 通过 请求的地址 url
    • 通过 请求方式 method
    • 请求参数 params | data 这几个值来做唯一判断
    • 如果两个请求上的这些信息都相同,那么就可以判断这两个请求是重复的
  • 获取 这个请求的 cancel 方法,调用 cancel 方法进行取消
    • 把当前请求和对应的 cancel 进行关联
    • 用对象、数组或者 Map 进行存储
      • 把当前请求的信息作为 key
      • 把对应的 cancel 方法作为 value
      • 存储到对象或者 Map
    • 根据当前请求信息,获取到对应的取消方法
      • 每一个请求的 cancel 方法需要调用 new CancelToken() 才可以获取

在页面离开时,丢弃所有未完成的请求 使用 AbortController

  • 访问一个请求会消耗比较长时间的页面,如果此时切换到其它页面,这些未完成的请求会影响下个页面的数据载入
  • 也就是说,下面页面中的请求会在前一个页面请求完成后才执行

原理简述

  • 页面中创建一个 AbortControlller 的实例
  • 在所有有可能需要结束请求的 config 里,添加 signal: abortController.sinal 这个选项
  • 当离开页面的时候,使用 abortController.abort() 结束所有的请求即可

使用数组 和 AbortController 实现

  • 请求列表 reqList
  • 删除重复请求的方法 rmSameReq
    • 通过判断 reqList 中是否有相同 methodurl 的项
    • 若重复 则调用该项中的 reqList[index].controller.abort() 方法 取消请求
    • abort() 被调用时,这个 promise 将 reject 一个名为 AbortError 的 DOMException
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// ts
type reqConfig = {
  [K in keyof AxiosRequestConfig]: AxiosRequestConfig[K]
}
const reqList: (reqConfig & { controller: AbortController })[] = [];

const rmSameReq = (method: string = '', url: string = ''): void => {
  const index = reqList.findIndex(v => v.method === method && v.url === url);
  if (index >= 0) {
    // 取消请求
    reqList[index].controller.abort();
    // 删除当前数据
    reqList.splice(index, 1);
  }
};

http.interceptors.request.use( config => {
  const {method, url} = config
    // 在push之前 遍历数组找到相同的请求取消掉
    rmSameReq(method, url)
    
    // 取消请求控制器
    const controller = new AbortController()
    
    // 记录取消操作的key
    config.signal = controller.signal
    
    // 每次的请求加入到数组 并带上控制器 闭包
    reqList.push( { ...config, controller } )
    return config
  }, /* error略 */
)

// 响应拦截器
http.interceptors.response.use(res => {
  	// 移除 成功请求记录 参数 res.config
  	const {method, url} = res.config
  	rmSameReq(method, url)
    return res.data;
}, err => {
  	// 移除 失败的请求记录 参数 err.config || {}
  	const {method, url} = err.config
  	rmSameReq(method, url)
  	
  	// 提示消息
    // axios.isCancel(err) || console.log(`重复请求信息:${err.message}`)
    return Promise.reject(err);
});

export default http;

使用 Map + axios.CancelToken 实现,略

额外封装一个 CancelRequest 类,导出,供拦截器实现使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
const instance = axios.create({ /* ... */ });

// 请求拦截器
instance.interceptors.request.use(config => {
  	// 检查之前是否存在相同的请求,如果存在则取消
  	// 记录当前请求 添加到 Map 中
    return config;
}, err => Promise.reject(err));

// 响应拦截器
instance.interceptors.response.use(res => {
  	// 移除 成功请求记录 参数 res.config
    return res.data;
}, err => {
  	// 移除 失败的请求记录 参数 err.config || {}
  	
  	// 提示消息
    // axios.isCancel(err) || console.log(`重复请求信息:${err.message}`)
    return Promise.reject(err);
});

export default instance;

参考


axios 响应错误后 重试请求 的封装

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
http.interceptors.response.use(() => void, function axiosRetryInterceptor(err) {
  const config = err.config;
  // 如果配置不存在或未设置重试选项,则拒绝
  if (!config || !config.retry) return Promise.reject(err);

  // 设置变量 跟踪重试次数
  config.__retryCount = config.__retryCount || 0;

  // 判断是否超过总重试次数
  if (config.__retryCount >= config.retry) {
    // 返回错误并退出自动重试
    return Promise.reject(err);
  }

  // 增加重试次数
  config.__retryCount += 1;

  // 打印当前重试次数
  // console.log(config.url + " 自动重试第" + config.__retryCount + "次");

  // 创建新的 Promise
  const backoff = new Promise(function (resolve) {
    setTimeout(function () {
      resolve();
    }, config.retryDelay || 1);
  });

  // 返回重试请求
  return backoff.then(function () {
    return axios(config);
  });
});

token 失效 返回登录页面 的封装

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 放入不需要拼接 token 的接口
const whiteList=['/login', '/logup']

axios.interceptors.request.use( config => config, error => Promise.reject(error));

axios.interceptors.response.use( res => {
  // 不包含请求的接口
  if(!whiteList.includes(res.config.url)) {
  // 判断返回的数据中的 code 或是 status
  
  // token失效返回的是401
  if(res.data.code === 401)
  
  // 清空存储的 token 值
  sessionStorage.removeItem('token')
 
  // 跳转登录页
  window.location('/login')
  }
  // 包含请求的接口则不做其他任何操作 正常返回数据即可
  const { data } = res;
  return data;
  }, error => Promise.reject(error) );

参考


6. axios 另一种取消请求功能…

需要为该请求配置一个 cancelToken,然后在外部调用一个 cancel 方法

  • CancelToken 废弃了,故略

参考


7. axios 接口封装在Vue中的应用

原理回顾

  • 页面中创建一个 AbortControlller 的实例
  • 在所有有可能需要结束请求的 config 里,添加 signal: abortController.sinal 这个选项
  • 当离开页面的时候,使用 abortController.abort() 结束所有的请求即可

vue代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import {defineComponent,} from 'vue';

export default defineComponent({
  setup(/*props, ctx*/) {
    const abortController = new AbortController();
    const etUser = function() {
      axios.post( `http://user/xxx`, formData, {
        headers: {
          'content-type': 'application/x-www-form-urlencoded'
        },
        signal: abortController.signal
      }).then(res => {})
    }
    
    // 在页面离开的时候,取消掉所有的这些请求即可,不管有没有完成
    onUnmounted(() => {
      abortController.abort() 
    });
    
    return {getRemoteSdp};
  },
})
  • 在离开这个页面的时,都会丢弃所有添加了 signal 的请求,不管页面中有没有未完成的请求,不会影响下一个页面的数据请求

封装到原型上

2.x 全局 API(Vue) 3.x 实例 API (app)
Vue.prototype app.config.globalProperties

参考


8. axios 封装为hooks


9. axios 接口封装总结



参考文章

相关文章

VueRequest 系列周边


  • 作者: Joel
  • 文章链接:
  • 版权声明
  • 非自由转载-非商用-非衍生-保持署名