[项目构建] 二次封装统一Axios配置 JSTS两个版本实现取消重复请求,超时重发

news/2024/5/7 15:05:32

前言

搭建项目时,每个项目都会axios来发请求,但是很多地方都需要发送请求,需要二次封装来解决可能需要处理一些通用的逻辑,比如统一的错误处理、请求拦截、响应拦截、设置请求头等。

axios二次封装基本需要四个方面

全局配置

Token、密钥

响应的统一处理

封装请求方法

全局配置

经常用的配置

baseURL:baseURL 将自动加在 url 前面,除非 url 是一个绝对 URL

timeout: timeout 指定请求超时的毫秒数, 如果请求时间超过 timeout 的值,则请求会被中断

responseType:表示浏览器将要响应的数据类型 , 选项包括: ‘arraybuffer’, ‘document’, ‘json’, ‘text’, ‘stream’

haeaders :自定义请求头

withCredentials: 表示跨域请求时是否需要使用凭证

let request= axios.create({baseURL: "http://localhost:8000/",timeout: 30 * 1000,responseType: "json",headers:{
"test": "this is globally configured request header"
},
withCredentials: true,
})

在请求拦截器中加入Token,密钥等 用md5加密一下

出于权限和安全考虑可能需要密钥
把他们在请求拦截器中设置到请求头

//使用axios的请求拦截器
request.interceptors.request.use((config)=> {
// token 一般存在于vuex/pinia或localStorage
let token = localStorage.getItem('token')
let url= config.url
if(!global.whiteListURl.includes(url) && token){
config.headers.token = token
}
// token一般是明文存储安全性不够,所以还有可能要设置密钥
const secretId = md5(global.secretId + new Date().toString())
config.headers.secretId = secretId
return config
},error => {
// 做个错误处理
return Promise.reject(new Error(error))
})

响应的统一基本处理,针对于错误的情况做统一全局处理

//使用axios响应拦截器
request.interceptors.request.use((res) => {
// 响应得做统一处理
const status = res.data.code || 200 ;const message = res.data.message || 'No messsage'//if(status === 401){//alert('你没有权限')// 设置你要跳转的页面 //router.push("/index")//return Promise.reject(new Error(message))//}// ....各种错误码不多举例了if(status !==200){alert(`错误码${status}+${message}`)return Promise.reject(new Error(message))}},error =>{alert('error')return Promise.reject(new Error(error))})
封装请求方法

把对接口的请求封装为方法

request.get=function (url,params,__object={}){return axios.get(url,{...params,__object})
}
request.post=function(url,params,__object){return axios.post(url, params, __object)
}
request.put = function(url,params,__object){return axios.put(url,parms,__object)
}request.delete= function(url,params,__object){return axios.delete(url,{parms,__object})
}
request.download = fucntion (url,params,__object){reutnr axios.post(url,params,{ ..._object, responseType: 'blob'})
}
...
基本封装四大项思路已经完成

我们可以用class类包装一下这四大项

为什么使用class类

通过将 Axios 实例和相关方法封装在一个类中,可以更清晰地组织代码,提高代码的可维护性和可读性,更好导出使用。

类可以实例化为对象,每个对象都有自己的状态和行为。这使得在应用程序中可以轻松地创建多个独立的客户端实例,并在它们之间共享或隔离状态。

import axios from "axios";
import global from '../global/index.ts'
import md5 from 'md5'
const config = {
// 使用别人的接口实验下 先把baseURL关闭
// baseURL: "http://localhost:8000/",timeout: 30 * 1000,responseType: "json",headers:{"test": "this is globally configured request header"},// withCredentials: true,}class RequestHttp {//实例对象service;constructor(config) {//this.service = axios.create(config);//请求拦截器this.service.interceptors.request.use((config) => {// token 一般存在于vuex/pinia或localStoragelet token = localStorage.getItem("token");let url = config.url;if (!global.whiteListURl.includes(url) && token) {config.headers.token = token;}// console.log(config.headers)const secretId = md5(global.secretId + new Date().toString());config.headers.secretId = secretId;return config;// token一般是明文存储安全性不够,所以还有可能要设置密钥},(error) => {// 做个错误处理return Promise.reject(new Error(error));});//使用axios响应拦截器this.service.interceptors.request.use((res) => {// 响应得做统一处理const status = res.code || 200;const message = res.msg || "No messsage";if (status === 401) {alert("你没有权限");// 设置你要跳转的页面// router.push("/index");return Promise.reject(new Error(message));}// ....各种错误码不多举例了if (status !== 200) {alert(`错误码${status}+${message}`);return Promise.reject(new Error(message));}return res},// 这里的错误返回可以具体些async (error) => {// 请求超时 && 网络错误单独判断,没有 responseif (error.message.indexOf("timeout") !== -1)alert("请求超时!请您稍后重试");if (error.message.indexOf("Network Error") !== -1)alert("网络错误!请您稍后重试");// 服务器结果都没有返回(可能服务器错误可能客户端断网),断网处理:可以跳转到断网页面if (!window.navigator.onLine) router.replace("/500");return Promise.reject(error);});}get(url, params?, _object = {}) {return this.service.get(url, { params, ..._object });}post(url, params, _object = {}) {return this.service.post(url, params, _object);}put(url, params, _object = {}) {return this.service.put(url, params, _object);}delete(url, params, _object = {}) {return this.service.delete(url, { params, ..._object });}download(url, params, _object = {}) {return this.service.post(url, params, { ..._object, responseType: "blob" });
}
}
export default new RequestHttp(config)

接下来 我们可以加入取消频繁重复请求的功能

原理很简单,定义一个数据结构存储url(或者更精确的),请求完再删掉,如果之后的请求url还存在就报错就行

这个功能有很多应用

function cancelRepaeatRequest(config){let hasRequest = []return ()=>{if(hasRequest.indexOf(config.url) !== -1){return Promise.reject(new Error('不要急'))}hasRequest.push(config.url)return request({...config}).then(()=>{hasRequest = hasRequest.filter((item)=>{if(item !== config.url){return item}})}))}
}
不过有更成熟的实现,其核心是通过 AbortController API来取消请求。

那么AbortController是什么呢
AbortController 是一个 JavaScript 中的内置对象,用于控制异步操作的中止,也用于取消正在进行的网络请求

//封装
const pendingMap = new Map()// 创建各请求唯一标识, 返回值类似:'/api:get',后续作为pendingMap的keyconst getUrl = (config) => {return [config.url, config.method].join(':')}class AbortAxios {// 添加控制器addPending(config) {this.removePending(config)const url = getUrl(config)// 创建控制器实例const abortController = new AbortController()// 定义对应signal标识config.signal = abortController.signalif (!pendingMap.has(url)) {pendingMap.set(url, abortController)}}// 清除重复请求removePending(config) {const url = getUrl(config)if (pendingMap.has(url)) {// 获取对应请求的控制器实例const abortController = pendingMap.get(url)// 取消请求abortController?.abort()// 清除出pendingMappendingMap.delete(url)}}}
export default new AbortAxios
在刚刚RequestHttp类中加入代码
//在请求拦截器加上
abortAxios.addPending(config)
//在响应拦截器加上
res && abortAxios.removePending(res.config)

接下来我们实现下超时重发

超时重发的意思是全局配置timeout配置后,如果请求超时会自动重新发送

那么如何知道请求超时错误呢

响应拦截器中错误err.message中有错误信息

error.message.indexOf("timeout") !== -1 使用这个来判断超时错误

实现起来也很简单 设置等待时间和重新发新发送次数再用Promise.then加定时器让请求在发送一次
在响应拦截器中的错误

//在响应拦截器中
if (error.message.indexOf("timeout") !== -1){const { waitTime, count } = { waitTime: 1000, count: 1};return retry(this.service, error, waitTime, count);// alert("请求超时!请您稍后重试");}
//新建文件axiosRetryexport default function retry(instance, error, waitTime, count) {const config = error.config;// 当前重复请求的次数
config.currentCount = config.currentCount ?? 0;
console.log(`${config.currentCount}次重连`);// 如果当前的重复请求次数已经大于规定次数,则返回Promise
if (config.currentCount >= count) {
return Promise.reject(error);
}
config.currentCount++;
// 等待间隔时间结束后再执行请求
return wait(waitTime).then(() => instance(config));
}
function wait(waitTime: number) {
return new Promise(resolve => setTimeout(resolve, waitTime));
}

最终实现TS版本 TS版本就是把上述代码加了类型规范

import {AxiosInstance,AxiosError,AxiosRequestConfig,InternalAxiosRequestConfig,AxiosResponse,} from 'axios'import axios from "axios";import global from '../global/index.ts'import md5 from 'md5'import abortAxios from './config/index.js'const config = {baseURL: "http://localhost:8000/",timeout: 30 * 1000,headers:{"test": "this is globally configured request header"},// withCredentials: true,}class RequestHttp {//实例对象service: AxiosInstance;constructor(config:AxiosRequestConfig) {//this.service = axios.create(config);//请求拦截器this.service.interceptors.request.use((config:InternalAxiosRequestConfig) => {abortAxios.addPending(config)// token 一般存在于vuex/pinia或localStoragelet token = localStorage.getItem("token");let url = config.url;if (!global.whiteListURl.includes(url) && token) {config.headers.token = token;}const secretId = md5(global.secretId + new Date().toString());config.headers.secretId = secretId;// abortAxios.removePending(config)return config;// token一般是明文存储安全性不够,所以还有可能要设置密钥},(error:AxiosError) => {// 做个错误处理return Promise.reject(error);});//使用axios响应拦截器this.service.interceptors.response.use((res:AxiosResponse) => {// 响应得做统一处理res && abortAxios.removePending(res.config)const {data} = resconst status = data.code || 200;const message = data.message || "No messsage";// ....各种错误码不多举例了if (status !== 200) {alert(`错误码${status}+${message}`);return Promise.reject(new Error(message));}return res},// 这里的错误返回可以具体些async (error:AxiosError) => {// 请求超时 && 网络错误单独判断,没有 responseif (error.message.indexOf("timeout") !== -1)
const { waitTime, count } = { waitTime: 1000, count: 1};
return retry(this.service, error, waitTime, count);
//alert("请求超时!请您稍后重试");if (error.message.indexOf("Network Error") !== -1)alert("网络错误!请您稍后重试");// 服务器结果都没有返回(可能服务器错误可能客户端断网),断网处理:可以跳转到断网页面// if (!window.navigator.onLine) router.replace("/500");return Promise.reject(error);});}get<T>(url:string, params:object, _object = {}):Promise<T> {return this.service.get(url, { params, ..._object });}post<T>(url:string, params:object| string, _object = {}):Promise<T> {return this.service.post(url, params, _object);}put<T>(url:string, params:object, _object = {}):Promise<T> {return this.service.put(url, params, _object);}delete<T>(url:string, params:any, _object = {}):Promise<T> {return this.service.delete(url, { params, ..._object });}download(url:string, params:object, _object = {}):Promise<BlobPart> {return this.service.post(url, params, { ..._object, responseType: "blob" });}}export default new RequestHttp(config)

文章到这里就结束了,希望对你有所帮助


http://www.mrgr.cn/p/31571000

相关文章

ansible作业

ansible作业 0.ansible了解 roles:多个角色的集合目录, 可以将多个的role,分别放至roles目录下的独立子目录中,如下示例 roles/mysql/nginx/tomcat/redis/默认roles存放路径/root/.ansible/roles /usr/share/ansible/roles /etc/ansible/rolesroles目录结构: playbook1.yml…

使用composer开发自己的扩展包

前言 日常的开发中我们经常用到composer去安装其他人封装好的扩展包&#xff0c;如果你有好的功能代码想分享给其他人使用&#xff0c;就可以使用composer打包成扩展包。其他人用composer安装后就可以使用你的扩展包了。这篇文章教你如何打包自己的composer扩展包。 1.新建仓…

【JAVA】PO、VO、DAO、BO、DTO、POJO你分得清吗?

在Java开发中&#xff0c;PO、VO、DAO、BO、DTO、POJO这些词汇是比较常见的&#xff0c;每个术语都有其特定的含义和用途。下面是它们的具体区别&#xff1a; 名称简要概况用途和特定PO (Persistence Object) 持…

【汇编语言】直接定址表

【汇编语言】直接定址表 文章目录 【汇编语言】直接定址表前言一、移位指令移位指令过程逻辑移位指令shl 和 shr 二、操作显存数据显示的原理显示缓冲区的结构显示信息的一种“直接”方式 三、描述内存单元的标号关于标号去了冒号的数据标号数据标号同时描述内存地址和单元长度…

实验一———美团APP

墨刀、Axure、Mockplus等原型设计工具优缺点分析: 一、墨刀 优点:在轻量级的移动端原型制作更加迅速,展示更加方便。 缺点:价格较贵,不能画流程图,相对于其他两款功能还不是很全面;应用局限性,专注于app原型设计,在后台和网页稍有乏力;归档能力不足,更倾向于链接、二…

LLM学习(5)——系统评估与优化

5.1 如何评估 LLM 应用 5.1.1 验证评估的一般思路 通过不断寻找Bad Case并进行针对性优化,将这些案例逐步加入验证集,形成一个具有一定样本数量的验证集。针对这种验证集,逐个进行评估变得不切实际,需要一种自动评估方法来对整体性能进行评估。验证迭代是构建以LLM为核心的…

1张图片+3090显卡微调Qwen-VL视觉语言大模型(仅做演示、效果还需加大数据量)

原项目地址&#xff1a;https://github.com/QwenLM/Qwen-VL/blob/master/README_CN.md 环境本地部署&#xff08;见之前博文&#xff09; 【本地部署 】23.08 阿里Qwen-VL&#xff1a;能对图片理解、定位物体、读取文字的视觉语言模型 (推理最低12G显存) 一、数据集格式说明 …

ESLlint重大更新后,使用旧版ESLint搭配Prettier的配置方式

概要 就在前几天&#xff0c;ESLint迎来了一次重大更新&#xff0c;9.0.0版本&#xff0c;根据官方文档介绍&#xff0c;使用新版的先决条件是Node.js版本必须是18.18.0、20.9.0&#xff0c;或者是>21.1.0的版本&#xff0c;新版ESLint将不再直接支持以下旧版配置(非扁平化…

Modbus转Profinet网关连接LED大屏与PLC通讯

Modbus转Profinet网关(XD-MDPN100)的主要功能是实现Modbus协议和Profinet协议之间的转换和通信。Modbus转Profinet网关集成了Modbus和Profinet两种协议,支持Modbus RTU主站/从站,并可以与RS485接口的设备,如变频器、智能高低压电器、电量测量装置等进行连接。通过Modbus转…

【Go语言】接口类型(一)接口类型与接口的值

本文是介绍golang接口类型的第一篇&#xff0c;主要介绍接口类型与接口类型的值的相关概念。 1. 静态类型、动态类型、动态值 所谓的静态类型&#xff08;即 static type&#xff09;&#xff0c;就是变量声明的时候的类型。 var age int // int 是静态类型 var name strin…

2024年最好用的10款ER图神器!

分享10款ER图工具,详细分析他们的功能特点、价格和适用场景,可以根据你的需求进行选择。ER图(Entity-Relationship Diagram)是数据库设计中常用的一种模型,用于描述实体之间的关系。这种图形化的表示方法旨在帮助人们理解和设计数据库结构,它们在数据库开发和设计中非常有…

【Network Automation系列】-- 第二章

继续上文,【Network Automation系列】-- 第一章 在第1章,对TCP/IP协议套件和Python的回顾中,我们研究了网络通信协议背后的理论和规范。 我们还快速浏览了一下Python语言。在本章中,我们将开始深入研究使用Python对网络设备的管理。 特别是,我们将研究使用Python以编程方式…

初中中考阅读理解难题一网打尽!句子结构深度解析+答案揭秘,助你轻松冲刺高分!-012

PDF格式公众号回复关键字:ZKYDT012原文1 Richard found the bird in the forest, didn’t he? 解析 1 Richard ,found 发现了, the bird 这只鸟, in the forest 在森林里, didn’t he? 不是吗 理查德在森林里发现了这只鸟,不是吗? 2 He saw a strange bird in a bush. 他…

【51单片机项目】基于51单片机自制多功能小键盘/模拟USB键盘【附源码】(STC89C52RC+CH9328)

目录 一、效果展示 二、创作灵感 三、硬件电路 注意事项 工作原理 四、源码 main.c 五、附录 CH9328工作原理 CH9328的模式选择 ​编辑 全键盘键码值表 参考链接 一、效果展示 该小键盘具有三种功能&#xff1a; 1、自动输入开机密码 2、每隔一段时间自动按下ct…

【Qt 专栏】DateTime日期时间组件

本文转载自:https://cloud.tencent.com.cn/developer/article/2371799 本章将重点介绍QDateTime日期与时间组件的常用方法及灵活运用。在Qt中,日期和时间的处理通常使用 QDateTime 类。QDateTime 是一个用于表示日期和时间的类,而与之相关的组件还包括 QDate 、 QTime以及QD…

【threejs教程7】threejs聚光灯、摄影机灯和汽车运动效果

【图片完整效果代码位于文章末】 在上一篇文章中我们实现了汽车模型的加载&#xff0c;这篇文章主要讲如何让汽车看起来像在运动。同时列出聚光灯和摄像机灯光的加载方法。 查看上一篇&#x1f449;【threejs教程6】threejs加载glb模型文件&#xff08;小米su7&#xff09;&…

七段数码管

import turtle, datetime def drawGap(): # 绘制数码管间隔 turtle.penup() turtle.fd(5) def drawLine(draw): # 绘制单段数码管 drawGap() turtle.pendown() if draw else turtle.penup() turtle.fd(40) drawGap() turtle.right(90) def drawDigit(d): # 根据数字绘制七段…

微软如何打造数字零售力航母系列科普02 --- 微软低代码应用平台加速企业创新 - 解放企业数字零售力

微软低代码应用平台推动企业创新- 解放企业数字零售力 微软在2023年GARTNER发布的魔力象限图中处于头部领先&#xff08;leader&#xff09;地位。 其LCAP产品是Microsoft Power Apps&#xff0c;扩展了AI Builder、Dataverse、Power Automate和Power Pages&#xff0c;这些都包…

git 命令怎么回退到指定的某个提交 commit hash 并推送远程分支?

问题 如下图&#xff0c;我要回退到 【002】Babel 的编译流程 这一次提交 解决 1、先执行下面命令&#xff0c;输出日志&#xff0c;主要就是拿到提交 commit 的 hash&#xff0c;上图红框即可 git log或者 vscode 里面直接右击&#xff0c;copy sha 2、执行下面命令回退 g…

条件格式实例

import pandas as pd import numpy as np import datetime todaystr(datetime.date.today())filepath/Users/kangyongqing/Documents/kangyq/202311/班均及合班储备/z拆班校验/ file102查询结果.csv file2工具-国家对应大洲表格_副本.xlsxdf1pd.read_csv(filepathfile1,dtypeob…