一篇文章带你详细了解axios的封装
axios 封装
对请求的封装在实际项目中是十分必要的,它可以让我们统一处理 http 请求。比如做一些拦截,处理一些错误等。本篇文章将详细介绍如何封装 axios 请求,具体实现的功能如下
基本配置
配置默认请求地址,超时等
请求拦截
拦截 request 请求,处理一些发送请求之前做的处理,譬如给 header 加 token 等
响应拦截
统一处理后端返回的错误
全局 loading
为所有请求加上全局 loading(可配置是否启用)
取消重复请求
当同样的请求还没返回结果再次请求直接取消
基础配置
这里以 vue3 为例,首先安装 axios,element-plus
npm i axios element-plus
在 src 下新建 http/request.ts 目录用于写我们的封装逻辑,然后调用 aixos 的 create 方法写一些基本配置
import axios, { AxiosResponse, InternalAxiosRequestConfig } from 'axios';
const service = axios.create({
method: 'get',
baseURL: import.meta.env.VITE_APP_API, //.env中的VITE_APP_API参数
headers: {
'Content-Type': 'application/json;charset=utf-8',
},
timeout: 10000, //超时时间
});
export default service;
这样便完成了 aixos 的基本配置,接下来我们可以在 http 下新建 api 目录用于存放我们接口请求,比如在 api 下创建 login.ts 用于写登录相关请求方法,这里的/auth/login我已经用 nestjs 写好了
import request from './request';
export const login = (data: any) => {
return request({
url: '/auth/login',
data,
method: 'post',
});
};
然后可以在页面进行调用
<script lang="ts" setup>
import { login } from '@/http/login';
const loginManage = async () => {
const data = await login({
username: '鸡哥哥',
password: '1234',
});
console.log(data);
};
loginManage();
</script>
结果打印如下
响应拦截器
我们可以看到返回的数据很多都是我们不需要的,我们需要的只有 data 中的数据,所以这时候我们便需要一个响应拦截器进行处理,同时在响应拦截器中我们不仅仅简单处理这个问题,还需要对后端返回的状态码进行判断,如果不是正确的状态码可以弹窗提示后端返回的描述(也可以自定义)
service.interceptors.response.use(
(res: AxiosResponse<any, any>) => {
const { data } = res;
if (data.code != 200) {
ElMessage({
message: data.describe,
type: 'error',
});
if (data.code === 401) {
//登录状态已过期.处理路由重定向
console.log('loginOut');
}
throw new Error(data.describe);
}
return data;
},
(error) => {
let { message } = error;
if (message == 'Network Error') {
message = '后端接口连接异常';
} else if (message.includes('timeout')) {
message = '系统接口请求超时';
} else if (message.includes('Request failed with status code')) {
message = '系统接口' + message.substr(message.length - 3) + '异常';
}
ElMessage({
message: message,
type: 'error',
});
return Promise.reject(error);
},
);
这里规定后台 code 不是 200 的请求是异常的,需要弹出异常信息(当然这里由自己规定),同时 401 状态表示登录已过期,如果你需要更多的异常处理都可以写在这里。注意这里都是对 code 状态码的判断,这表示后台返回的 http 的 status 都是 2xx 才会进入的逻辑判断,如果后台返回 status 异常状态码比如 4xx,3xx 等就会进入 error 里,可以在 error 里进行逻辑处理,这里要和后端小朋友约定好
请求拦截器
请求请求拦截器和响应拦截器类似,只不过是在请求发送之前我们需要做哪些处理,它的用法如下
service.interceptors.request.use(
(config: InternalAxiosRequestConfig<any>) => {
console.log(config);
return config;
},
(error) => {
console.log(error);
},
);
我们可以看到 config 中包含了我们请求的一些信息像 headers,data 等等,我们是可以在这里对其进行修改的,比如我们在 headers 加一个 token
declare module "axios" {
interface InternalAxiosRequestConfig<D = any, T = any> {
isToken?: boolean;
}
}
declare module "axios" {
interface AxiosRequestConfig<D = any> {
isToken?: boolean;
}
}
service.interceptors.request.use(
(config: InternalAxiosRequestConfig<any>) => {
const { isToken = true } = config;
if (localStorage.getItem('token') && !isToken) {
config.headers['Authorization'] =
'Bearer ' + localStorage.getItem('token'); // 让每个请求携带自定义token 请根据实际情况自行修改
}
return config;
},
(error) => {
console.log(error);
},
);
这里假设用户登录成功将 token 缓存到了 localStorage 中,接口是否需要 token 则是在请求的时候自己配置,比如 login 接口不加 token,注意这里需要给InternalAxiosRequestConfig和AxiosRequestConfig加上自定义的字段,否则 TS 会报错
export const login = (data: any) => {
return request({
url: '/auth/login',
data,
isToken: false,
method: 'post',
});
};
此时我们可以获取到 config 中的 isToken 了
添加全局 loading
我们通常会在请求开始前加上 loading 弹窗,请求结束再进行关闭,实现其实很简单,在请求拦截器中调用 ElLoading.service 实例,响应拦截器中 close 即可。但是这样会出现一个问题,
当多个请求进入会开启多个 loading 实例吗? 这倒不会,因为在 element-plus 中的 ElLoading.service()是单例模式,只会开启一个 loading。
上述问题虽然不需要我们考虑,但是还有一个问题
同时进来多个请求,此时 loading 已经开启,假设 1 秒后多个请求中其中一个请求请求完成,按照上述逻辑会执行 close 方法,但是还有请求未完成 loading 却已经关闭,显然这不符合我们的期望
因此,我们可以定义一个变量用于记录正在请求的数量,当该变量为 1 时开启 loading,当变量为 0 时关闭 loading,同样的我们还定义了 config 中的 loading 让开发者自己决定是否开启 loading,实现如下
let requestCount = 0;
const showLoading = () => {
requestCount++;
if (requestCount === 1) loadingInstance();
};
const closeLoading = () => {
requestCount--;
if (requestCount === 0) loadingInstance().close();
};
service.interceptors.request.use(
(config: InternalAxiosRequestConfig<any>) => {
const { loading = true, isToken = true } = config;
return config;
},
(error) => {
console.log(error);
},
);
service.interceptors.response.use(
(res: AxiosResponse<any, any>) => {
const { data, config } = res;
const { loading = true } = config;
if (loading) closeLoading();
},
(error) => {
closeLoading();
return Promise.reject(error);
},
);
取消重复请求
当同样的请求还没返回结果再次请求我们需要直接取消这个请求,通常发生在用户连续点击然后请求接口的情况,但是如果加了 loading 这种情况就不会发生。axios 中取消请求可以使用AbortController,注意这个 api 需要 axios 版本大于 v0.22.0 才可使用,低版本可以使用CancelToken,下面看一下AbortController使用方法
service.interceptors.request.use(
(config: InternalAxiosRequestConfig<any>) => {
const controller = new AbortController();
const { loading = true, isToken = true } = config;
config.signal = controller.signal;
controller.abort();
return config;
},
(error) => {
console.log(error);
},
);
这里是将 controller 的 signal 赋值给 config 的 sigal,然后执行 controller 的 abort 函数即可取消请求
知道了如何取消 axios 请求,接下来我们就可以写取消重复请求的逻辑了
当拦截到请求的时候,将 config 中的 data,url 作为 key 值,AbortController 实例作为 value 存在一个 map 中,判断是否 key 值是否存在来决定是取消请求还是保存实例
const requestMap = new Map();
service.interceptors.request.use(
(config: InternalAxiosRequestConfig<any>) => {
const controller = new AbortController();
const key = config.data + config.url;
config.signal = controller.signal;
if (requestMap.has(key)) {
requestMap.get(key).abort();
requestMap.delete(key);
} else {
requestMap.set(key, controller);
}
return config;
},
(error) => {
console.log(error);
},
);
我们短时间内发送两次请求就会发现有一个请求被取消了
到这里基本就完成了 axios 的封装,下面是完整代码,直接 CV,就可以摸鱼一整天~
import axios, {
AxiosInstance,
AxiosResponse,
InternalAxiosRequestConfig,
} from "axios";
import { ElMessage, ElLoading } from "element-plus";
const loadingInstance = ElLoading.service;
let requestCount = 0;
const showLoading = () => {
requestCount++;
if (requestCount === 1) loadingInstance();
};
const closeLoading = () => {
requestCount--;
if (requestCount === 0) loadingInstance().close();
};
const service: AxiosInstance = axios.create({
method: "get",
baseURL: import.meta.env.VITE_APP_API,
headers: {
"Content-Type": "application/json;charset=utf-8",
},
timeout: 10000,
});
//请求拦截
declare module "axios" {
interface InternalAxiosRequestConfig<D = any, T = any> {
loading?: boolean;
isToken?: boolean;
}
}
declare module "axios" {
interface AxiosRequestConfig<D = any> {
loading?: boolean;
isToken?: boolean;
}
}
const requestMap = new Map();
service.interceptors.request.use(
(config: InternalAxiosRequestConfig<any>) => {
const controller = new AbortController();
const key = config.data + config.url;
config.signal = controller.signal;
if (requestMap.has(key)) {
requestMap.get(key).abort();
requestMap.delete(key);
} else {
requestMap.set(key, controller);
}
console.log(123);
const { loading = true, isToken = true } = config;
if (loading) showLoading();
if (localStorage.getItem("token") && !isToken) {
config.headers["Authorization"] =
"Bearer " + localStorage.getItem("token"); // 让每个请求携带自定义token 请根据实际情况自行修改
}
return config;
},
(error) => {
console.log(error);
}
);
service.interceptors.response.use(
(res: AxiosResponse<any, any>) => {
const { data, config } = res;
const { loading = true } = config;
if (loading) closeLoading();
if (data.code != 200) {
ElMessage({
message: data.describe,
type: "error",
});
if (data.code === 401) {
//登录状态已过期.处理路由重定向
console.log("loginOut");
}
throw new Error(data.describe);
}
return data;
},
(error) => {
closeLoading();
let { message } = error;
if (message == "Network Error") {
message = "后端接口连接异常";
} else if (message.includes("timeout")) {
message = "系统接口请求超时";
} else if (message.includes("Request failed with status code")) {
message = "系统接口" + message.substr(message.length - 3) + "异常";
}
ElMessage({
message: message,
type: "error",
});
return Promise.reject(error);
}
);
export default service;
关注公众号web前端进阶每日更新最新前端技术文章,你想要的都有!
一篇文章带你详细了解axios的封装的更多相关文章
- MYSQL(基本篇)——一篇文章带你走进MYSQL的奇妙世界
MYSQL(基本篇)--一篇文章带你走进MYSQL的奇妙世界 MYSQL算是我们程序员必不可少的一份求职工具了 无论在什么岗位,我们都可以看到应聘要求上所书写的"精通MYSQL等数据库及优化 ...
- MYSQL(进阶篇)——一篇文章带你深入掌握MYSQL
MYSQL(进阶篇)--一篇文章带你深入掌握MYSQL 我们在上篇文章中已经学习了MYSQL的基本语法和概念 在这篇文章中我们将讲解底层结构和一些新的语法帮助你更好的运用MYSQL 温馨提醒:该文章大 ...
- 一篇文章带你掌握主流基础框架——Spring
一篇文章带你掌握主流基础框架--Spring 这篇文章中我们将会介绍Spring的框架以及本体内容,包括核心容器,注解开发,AOP以及事务等内容 那么简单说明一下Spring的必要性: Spring技 ...
- 一篇文章带你掌握主流办公框架——SpringBoot
一篇文章带你掌握主流办公框架--SpringBoot 在之前的文章中我们已经学习了SSM的全部内容以及相关整合 SSM是Spring的产品,主要用来简化开发,但我们现在所介绍的这款框架--Spring ...
- 一篇文章带你了解网页框架——Vue简单入门
一篇文章带你了解网页框架--Vue简单入门 这篇文章将会介绍我们前端入门级别的框架--Vue的简单使用 如果你以后想从事后端程序员,又想要稍微了解前端框架知识,那么这篇文章或许可以给你带来帮助 温馨提 ...
- 一篇文章带你了解热门版本控制系统——Git
一篇文章带你了解热门版本控制系统--Git 这篇文章会介绍到关于版本控制的相关知识以及版本控制神器Git 我们可能在生活中经常会使用GitHub网页去查询一些开源的资源或者项目,GitHub就是基于G ...
- 一篇文章带你了解服务器操作系统——Linux简单入门
一篇文章带你了解服务器操作系统--Linux简单入门 Linux作为服务器的常用操作系统,身为工作人员自然是要有所了解的 在本篇中我们会简单介绍Linux的特点,安装,相关指令使用以及内部程序的安装等 ...
- 一篇文章带你了解轻量级Web服务器——Nginx简单入门
一篇文章带你了解轻量级Web服务器--Nginx简单入门 Nginx是一款轻量级的Web服务器/反向代理服务器及电子邮件代理服务器 在本篇中我们会简单介绍Nginx的特点,安装,相关指令使用以及配置信 ...
- 一篇文章带你掌握主流数据库框架——MyBatis
一篇文章带你掌握主流数据库框架--MyBatis MyBatis 是一款优秀的持久层框架,它支持自定义 SQL.存储过程以及高级映射. 在之前的文章中我们学习了MYSQL和JDBC,但是这些东西远远不 ...
- 一篇文章带你掌握主流服务层框架——SpringMVC
一篇文章带你掌握主流服务层框架--SpringMVC 在之前的文章中我们已经学习了Spring的基本内容,SpringMVC隶属于Spring的一部分内容 但由于SpringMVC完全针对于服务层使用 ...
随机推荐
- 超全!Python图形界面框架PyQt5使用指南!
使用Python开发图形界面的软件其实并不多,相对于GUI界面,可能Web方式的应用更受人欢迎.但对于像我一样对其他编程语言比如C#或WPF并不熟悉的人来说,未必不是一个好的工具. 常见GUI框架 P ...
- java多线程--2 静态代理、Lambda表达式
java多线程--2 静态代理.Lambda表达式 静态代理 package com.ssl.demo02; //静态代理 //真实对象和代理对象都要实现同一个接口 //代理对象必须要代理真实角色 / ...
- 用ACDSee查看Office文档?No!有中文解决方案吗?暂未发现!
看图软件选择 用过不少看图软件,20年前就觉得ACDSee实在太好用了,界面漂亮.速度快.格式多.体积小! 后来图像格式越来越丰富,ACDSee版本也越来越新,体积越来越大. 看图软件也越来越繁杂,免 ...
- 基于SpringBoot实现单元测试的多种情境/方法(二)
本文分享自天翼云开发者社区@<基于SpringBoot实现单元测试的多种情境/方法(二)>, 作者:才开始学技术的小白 1 Mock基础回顾 在上一篇分享中我们详细介绍了简单的.用moc ...
- .NET CORE 部署到IIS上,HTTP 错误 500.19 - Internal Server Error
经排查,是因为项目中web.config的rewrite节点不支持,注释掉此节点即可,或者尝试下载相关依赖以支持此节点
- mariabackup -prepare step on increment backup failed
问题描述:使用mariabackup对maridb10.6.4进行物理备份,进行增量恢复的时候报错.截止到目前,还是mariadb的一个bug,还没有修复.在增备的过程中如果出现新库的建立,数据库就会 ...
- sql 开窗函数排序遇到空值的处理办法
sql sever默认null最小 升序排序 null值在最前面,若要放在后面,则: order by case when col is null then 1 else 0 end, col 降序排 ...
- CommunityToolkit.Mvvm8.1 消息通知(4)
本系列文章导航 https://www.cnblogs.com/aierong/p/17300066.html https://github.com/aierong/WpfDemo (自我Demo地址 ...
- C++ sizeof与strlen,并借此明晰内存对齐
前言 sizeof()与strlen()都是为了获取对象的长度.在正常编写C++的算法程序代码时,可能这两个都很少用到,因为各种stl容器的封装已经给了我们很大的便利,比如我们在想要获取自定义的vec ...
- TiDB Lightning导入超大型txt文件实践
背景 TiDB 提供了很多种数据迁移的方式,但这些工具/方案普遍对MySQL比较友好,一旦涉及到异构数据迁移,就不得不另寻出路,借助各种开源或商业的数据同步工具.其实数据在不同系统的流转当中,有一种格 ...