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

发布于:2024-04-29 ⋅ 阅读:(33) ⋅ 点赞:(0)

前言

搭建项目时,每个项目都会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或localStorage

let 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) => {

// 请求超时 && 网络错误单独判断,没有 response

if (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的key

const 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.signal

if (!pendingMap.has(url)) {

pendingMap.set(url, abortController)

}

}

// 清除重复请求

removePending(config) {

const url = getUrl(config)

if (pendingMap.has(url)) {

// 获取对应请求的控制器实例

const abortController = pendingMap.get(url)

// 取消请求

abortController?.abort()

// 清除出pendingMap

pendingMap.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("请求超时!请您稍后重试");

}
//新建文件axiosRetry


export 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或localStorage

let 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} = res

const 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) => {

// 请求超时 && 网络错误单独判断,没有 response

if (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)

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