axios 封装

对请求的封装在实际项目中是十分必要的,它可以让我们统一处理 http 请求。比如做一些拦截,处理一些错误等。本篇文章将详细介绍如何封装 axios 请求,具体实现的功能如下

  • 基本配置

    配置默认请求地址,超时等

  • 请求拦截

    拦截 request 请求,处理一些发送请求之前做的处理,譬如给 header 加 token 等

  • 响应拦截

    统一处理后端返回的错误

  • 全局 loading

    为所有请求加上全局 loading(可配置是否启用)

  • 取消重复请求

当同样的请求还没返回结果再次请求直接取消

基础配置

这里以 vue3 为例,首先安装 axios,element-plus

  1. npm i axios element-plus

在 src 下新建 http/request.ts 目录用于写我们的封装逻辑,然后调用 aixos 的 create 方法写一些基本配置

  1. import axios, { AxiosResponse, InternalAxiosRequestConfig } from 'axios';
  2. const service = axios.create({
  3. method: 'get',
  4. baseURL: import.meta.env.VITE_APP_API, //.env中的VITE_APP_API参数
  5. headers: {
  6. 'Content-Type': 'application/json;charset=utf-8',
  7. },
  8. timeout: 10000, //超时时间
  9. });
  10. export default service;

这样便完成了 aixos 的基本配置,接下来我们可以在 http 下新建 api 目录用于存放我们接口请求,比如在 api 下创建 login.ts 用于写登录相关请求方法,这里的/auth/login我已经用 nestjs 写好了

  1. import request from './request';
  2. export const login = (data: any) => {
  3. return request({
  4. url: '/auth/login',
  5. data,
  6. method: 'post',
  7. });
  8. };

然后可以在页面进行调用

  1. <script lang="ts" setup>
  2. import { login } from '@/http/login';
  3. const loginManage = async () => {
  4. const data = await login({
  5. username: '鸡哥哥',
  6. password: '1234',
  7. });
  8. console.log(data);
  9. };
  10. loginManage();
  11. </script>

结果打印如下

响应拦截器

我们可以看到返回的数据很多都是我们不需要的,我们需要的只有 data 中的数据,所以这时候我们便需要一个响应拦截器进行处理,同时在响应拦截器中我们不仅仅简单处理这个问题,还需要对后端返回的状态码进行判断,如果不是正确的状态码可以弹窗提示后端返回的描述(也可以自定义)

  1. service.interceptors.response.use(
  2. (res: AxiosResponse<any, any>) => {
  3. const { data } = res;
  4. if (data.code != 200) {
  5. ElMessage({
  6. message: data.describe,
  7. type: 'error',
  8. });
  9. if (data.code === 401) {
  10. //登录状态已过期.处理路由重定向
  11. console.log('loginOut');
  12. }
  13. throw new Error(data.describe);
  14. }
  15. return data;
  16. },
  17. (error) => {
  18. let { message } = error;
  19. if (message == 'Network Error') {
  20. message = '后端接口连接异常';
  21. } else if (message.includes('timeout')) {
  22. message = '系统接口请求超时';
  23. } else if (message.includes('Request failed with status code')) {
  24. message = '系统接口' + message.substr(message.length - 3) + '异常';
  25. }
  26. ElMessage({
  27. message: message,
  28. type: 'error',
  29. });
  30. return Promise.reject(error);
  31. },
  32. );

这里规定后台 code 不是 200 的请求是异常的,需要弹出异常信息(当然这里由自己规定),同时 401 状态表示登录已过期,如果你需要更多的异常处理都可以写在这里。注意这里都是对 code 状态码的判断,这表示后台返回的 http 的 status 都是 2xx 才会进入的逻辑判断,如果后台返回 status 异常状态码比如 4xx,3xx 等就会进入 error 里,可以在 error 里进行逻辑处理,这里要和后端小朋友约定好

请求拦截器

请求请求拦截器和响应拦截器类似,只不过是在请求发送之前我们需要做哪些处理,它的用法如下

  1. service.interceptors.request.use(
  2. (config: InternalAxiosRequestConfig<any>) => {
  3. console.log(config);
  4. return config;
  5. },
  6. (error) => {
  7. console.log(error);
  8. },
  9. );

我们可以看到 config 中包含了我们请求的一些信息像 headers,data 等等,我们是可以在这里对其进行修改的,比如我们在 headers 加一个 token

  1. declare module "axios" {
  2. interface InternalAxiosRequestConfig<D = any, T = any> {
  3. isToken?: boolean;
  4. }
  5. }
  6. declare module "axios" {
  7. interface AxiosRequestConfig<D = any> {
  8. isToken?: boolean;
  9. }
  10. }
  11. service.interceptors.request.use(
  12. (config: InternalAxiosRequestConfig<any>) => {
  13. const { isToken = true } = config;
  14. if (localStorage.getItem('token') && !isToken) {
  15. config.headers['Authorization'] =
  16. 'Bearer ' + localStorage.getItem('token'); // 让每个请求携带自定义token 请根据实际情况自行修改
  17. }
  18. return config;
  19. },
  20. (error) => {
  21. console.log(error);
  22. },
  23. );

这里假设用户登录成功将 token 缓存到了 localStorage 中,接口是否需要 token 则是在请求的时候自己配置,比如 login 接口不加 token,注意这里需要给InternalAxiosRequestConfigAxiosRequestConfig加上自定义的字段,否则 TS 会报错

  1. export const login = (data: any) => {
  2. return request({
  3. url: '/auth/login',
  4. data,
  5. isToken: false,
  6. method: 'post',
  7. });
  8. };

此时我们可以获取到 config 中的 isToken 了

添加全局 loading

我们通常会在请求开始前加上 loading 弹窗,请求结束再进行关闭,实现其实很简单,在请求拦截器中调用 ElLoading.service 实例,响应拦截器中 close 即可。但是这样会出现一个问题,

当多个请求进入会开启多个 loading 实例吗? 这倒不会,因为在 element-plus 中的 ElLoading.service()是单例模式,只会开启一个 loading。

上述问题虽然不需要我们考虑,但是还有一个问题

同时进来多个请求,此时 loading 已经开启,假设 1 秒后多个请求中其中一个请求请求完成,按照上述逻辑会执行 close 方法,但是还有请求未完成 loading 却已经关闭,显然这不符合我们的期望

因此,我们可以定义一个变量用于记录正在请求的数量,当该变量为 1 时开启 loading,当变量为 0 时关闭 loading,同样的我们还定义了 config 中的 loading 让开发者自己决定是否开启 loading,实现如下

  1. let requestCount = 0;
  2. const showLoading = () => {
  3. requestCount++;
  4. if (requestCount === 1) loadingInstance();
  5. };
  6. const closeLoading = () => {
  7. requestCount--;
  8. if (requestCount === 0) loadingInstance().close();
  9. };
  10. service.interceptors.request.use(
  11. (config: InternalAxiosRequestConfig<any>) => {
  12. const { loading = true, isToken = true } = config;
  13. return config;
  14. },
  15. (error) => {
  16. console.log(error);
  17. },
  18. );
  19. service.interceptors.response.use(
  20. (res: AxiosResponse<any, any>) => {
  21. const { data, config } = res;
  22. const { loading = true } = config;
  23. if (loading) closeLoading();
  24. },
  25. (error) => {
  26. closeLoading();
  27. return Promise.reject(error);
  28. },
  29. );

取消重复请求

当同样的请求还没返回结果再次请求我们需要直接取消这个请求,通常发生在用户连续点击然后请求接口的情况,但是如果加了 loading 这种情况就不会发生。axios 中取消请求可以使用AbortController,注意这个 api 需要 axios 版本大于 v0.22.0 才可使用,低版本可以使用CancelToken,下面看一下AbortController使用方法

  1. service.interceptors.request.use(
  2. (config: InternalAxiosRequestConfig<any>) => {
  3. const controller = new AbortController();
  4. const { loading = true, isToken = true } = config;
  5. config.signal = controller.signal;
  6. controller.abort();
  7. return config;
  8. },
  9. (error) => {
  10. console.log(error);
  11. },
  12. );

这里是将 controller 的 signal 赋值给 config 的 sigal,然后执行 controller 的 abort 函数即可取消请求

知道了如何取消 axios 请求,接下来我们就可以写取消重复请求的逻辑了

当拦截到请求的时候,将 config 中的 data,url 作为 key 值,AbortController 实例作为 value 存在一个 map 中,判断是否 key 值是否存在来决定是取消请求还是保存实例

  1. const requestMap = new Map();
  2. service.interceptors.request.use(
  3. (config: InternalAxiosRequestConfig<any>) => {
  4. const controller = new AbortController();
  5. const key = config.data + config.url;
  6. config.signal = controller.signal;
  7. if (requestMap.has(key)) {
  8. requestMap.get(key).abort();
  9. requestMap.delete(key);
  10. } else {
  11. requestMap.set(key, controller);
  12. }
  13. return config;
  14. },
  15. (error) => {
  16. console.log(error);
  17. },
  18. );

我们短时间内发送两次请求就会发现有一个请求被取消了

到这里基本就完成了 axios 的封装,下面是完整代码,直接 CV,就可以摸鱼一整天~

  1. import axios, {
  2. AxiosInstance,
  3. AxiosResponse,
  4. InternalAxiosRequestConfig,
  5. } from "axios";
  6. import { ElMessage, ElLoading } from "element-plus";
  7. const loadingInstance = ElLoading.service;
  8. let requestCount = 0;
  9. const showLoading = () => {
  10. requestCount++;
  11. if (requestCount === 1) loadingInstance();
  12. };
  13. const closeLoading = () => {
  14. requestCount--;
  15. if (requestCount === 0) loadingInstance().close();
  16. };
  17. const service: AxiosInstance = axios.create({
  18. method: "get",
  19. baseURL: import.meta.env.VITE_APP_API,
  20. headers: {
  21. "Content-Type": "application/json;charset=utf-8",
  22. },
  23. timeout: 10000,
  24. });
  25. //请求拦截
  26. declare module "axios" {
  27. interface InternalAxiosRequestConfig<D = any, T = any> {
  28. loading?: boolean;
  29. isToken?: boolean;
  30. }
  31. }
  32. declare module "axios" {
  33. interface AxiosRequestConfig<D = any> {
  34. loading?: boolean;
  35. isToken?: boolean;
  36. }
  37. }
  38. const requestMap = new Map();
  39. service.interceptors.request.use(
  40. (config: InternalAxiosRequestConfig<any>) => {
  41. const controller = new AbortController();
  42. const key = config.data + config.url;
  43. config.signal = controller.signal;
  44. if (requestMap.has(key)) {
  45. requestMap.get(key).abort();
  46. requestMap.delete(key);
  47. } else {
  48. requestMap.set(key, controller);
  49. }
  50. console.log(123);
  51. const { loading = true, isToken = true } = config;
  52. if (loading) showLoading();
  53. if (localStorage.getItem("token") && !isToken) {
  54. config.headers["Authorization"] =
  55. "Bearer " + localStorage.getItem("token"); // 让每个请求携带自定义token 请根据实际情况自行修改
  56. }
  57. return config;
  58. },
  59. (error) => {
  60. console.log(error);
  61. }
  62. );
  63. service.interceptors.response.use(
  64. (res: AxiosResponse<any, any>) => {
  65. const { data, config } = res;
  66. const { loading = true } = config;
  67. if (loading) closeLoading();
  68. if (data.code != 200) {
  69. ElMessage({
  70. message: data.describe,
  71. type: "error",
  72. });
  73. if (data.code === 401) {
  74. //登录状态已过期.处理路由重定向
  75. console.log("loginOut");
  76. }
  77. throw new Error(data.describe);
  78. }
  79. return data;
  80. },
  81. (error) => {
  82. closeLoading();
  83. let { message } = error;
  84. if (message == "Network Error") {
  85. message = "后端接口连接异常";
  86. } else if (message.includes("timeout")) {
  87. message = "系统接口请求超时";
  88. } else if (message.includes("Request failed with status code")) {
  89. message = "系统接口" + message.substr(message.length - 3) + "异常";
  90. }
  91. ElMessage({
  92. message: message,
  93. type: "error",
  94. });
  95. return Promise.reject(error);
  96. }
  97. );
  98. export default service;

关注公众号web前端进阶每日更新最新前端技术文章,你想要的都有!

一篇文章带你详细了解axios的封装的更多相关文章

  1. MYSQL(基本篇)——一篇文章带你走进MYSQL的奇妙世界

    MYSQL(基本篇)--一篇文章带你走进MYSQL的奇妙世界 MYSQL算是我们程序员必不可少的一份求职工具了 无论在什么岗位,我们都可以看到应聘要求上所书写的"精通MYSQL等数据库及优化 ...

  2. MYSQL(进阶篇)——一篇文章带你深入掌握MYSQL

    MYSQL(进阶篇)--一篇文章带你深入掌握MYSQL 我们在上篇文章中已经学习了MYSQL的基本语法和概念 在这篇文章中我们将讲解底层结构和一些新的语法帮助你更好的运用MYSQL 温馨提醒:该文章大 ...

  3. 一篇文章带你掌握主流基础框架——Spring

    一篇文章带你掌握主流基础框架--Spring 这篇文章中我们将会介绍Spring的框架以及本体内容,包括核心容器,注解开发,AOP以及事务等内容 那么简单说明一下Spring的必要性: Spring技 ...

  4. 一篇文章带你掌握主流办公框架——SpringBoot

    一篇文章带你掌握主流办公框架--SpringBoot 在之前的文章中我们已经学习了SSM的全部内容以及相关整合 SSM是Spring的产品,主要用来简化开发,但我们现在所介绍的这款框架--Spring ...

  5. 一篇文章带你了解网页框架——Vue简单入门

    一篇文章带你了解网页框架--Vue简单入门 这篇文章将会介绍我们前端入门级别的框架--Vue的简单使用 如果你以后想从事后端程序员,又想要稍微了解前端框架知识,那么这篇文章或许可以给你带来帮助 温馨提 ...

  6. 一篇文章带你了解热门版本控制系统——Git

    一篇文章带你了解热门版本控制系统--Git 这篇文章会介绍到关于版本控制的相关知识以及版本控制神器Git 我们可能在生活中经常会使用GitHub网页去查询一些开源的资源或者项目,GitHub就是基于G ...

  7. 一篇文章带你了解服务器操作系统——Linux简单入门

    一篇文章带你了解服务器操作系统--Linux简单入门 Linux作为服务器的常用操作系统,身为工作人员自然是要有所了解的 在本篇中我们会简单介绍Linux的特点,安装,相关指令使用以及内部程序的安装等 ...

  8. 一篇文章带你了解轻量级Web服务器——Nginx简单入门

    一篇文章带你了解轻量级Web服务器--Nginx简单入门 Nginx是一款轻量级的Web服务器/反向代理服务器及电子邮件代理服务器 在本篇中我们会简单介绍Nginx的特点,安装,相关指令使用以及配置信 ...

  9. 一篇文章带你掌握主流数据库框架——MyBatis

    一篇文章带你掌握主流数据库框架--MyBatis MyBatis 是一款优秀的持久层框架,它支持自定义 SQL.存储过程以及高级映射. 在之前的文章中我们学习了MYSQL和JDBC,但是这些东西远远不 ...

  10. 一篇文章带你掌握主流服务层框架——SpringMVC

    一篇文章带你掌握主流服务层框架--SpringMVC 在之前的文章中我们已经学习了Spring的基本内容,SpringMVC隶属于Spring的一部分内容 但由于SpringMVC完全针对于服务层使用 ...

随机推荐

  1. 【Visual Leak Detector】QT 中 VLD 输出解析(一)

    说明 使用 VLD 内存泄漏检测工具辅助开发时整理的学习笔记. 目录 说明 1. 使用方式 2. 无内存泄漏时的输出报告 1. 使用方式 在 QT 中使用 VLD 的方法可以查看另外几篇博客: [Vi ...

  2. SQL Server底层架构技术对比

    背景 数据库是信息化的基石,支撑着整个业务系统,发挥着非常重要的作用,被喻为"IT的心脏".因此,让数据库安全.稳定.高效地运行已经成为IT管理者必须要面对的问题.数据库在底层架构 ...

  3. 基于docker和cri-dockerd部署k8sv1.26.3

    cri-dockerd是什么? 在 Kubernetes v1.24 及更早版本中,我们使用docker作为容器引擎在k8s上使用时,依赖一个dockershim的内置k8s组件:k8s v1.24发 ...

  4. 六位一体Serverless化应用,帮你摆脱服务器的烦恼

    ​ 随着互联网技术的飞速发展,越来越多的应用横空出世,是以不可避免带来了大量的服务器需求.大部分的开发者都选择购买或者租用服务器,然而这样也带来了诸多的烦恼. 1.硬件成本高昂 购买服务器费用昂贵,除 ...

  5. LabVIEW Actor Framwork (2)________ 边学边做server&client

    回顾下初始需求: 现在要做一个类似聊天的demo,一个server端,若干个client端:首先是server启动,通过server可以打开若干个client端,然后每个client可以独立给serv ...

  6. TypeScript 学习笔记 — 自定义类型:部分属性可选,反选 key,求对象交差并补集等(十三)

    目录 将部分属性变为可选属性 根据值的类型 反选 key 写法一:基础原理写法,使用不同的内置类型,Pick 和 Omit 写法二:基础原理写法,使用 Pick 内置类型 + 传参的方式 写法三:使用 ...

  7. it必给装机小软件附源码

    需要的包 启动之后是这个样子的 远吗如下: #authon fengimport zipfile as zfimport osimport win32apiimport win32conimport ...

  8. Redis集群介绍及测试思路

    作者:京东零售 李磊 Redis集群介绍 Redis集群一般有四种方式,分别为:主从复制.哨兵模式.Cluster以及各大厂的集群方案.在3.0版本之前只支持单实例模式,3.0之后支持了集群方式.在3 ...

  9. [GIT]辨析/区别: git reset HEAD 与 git reset --hard HEAD | 版本回撤

    1 场景1: 撤销到远程仓库或本地仓库的最新最近一次的正式版本 1.1 文由 时常有这样一种场景,不小心改动了部分文件,或修改了部分文件却发现无用,此时可能还没有git push,也可能push了:又 ...

  10. 教程 - 在 Vue3+Ts 中引入 CesiumJS 的最佳实践@2023

    目录 1. 本篇适用范围与目的 1.1. 适用范围 1.2. 目的 2. 牛刀小试 - 先看到地球 2.1. 创建 Vue3 - TypeScript 工程并安装 cesium 2.2. 清理不必要的 ...