前言
搭建项目时,每个项目都会axios来发请求,但是很多地方都需要发送请求,需要二次封装来解决可能需要处理一些通用的逻辑,比如统一的错误处理、请求拦截、响应拦截、设置请求头等。
axios二次封装基本需要四个方面
全局配置
Token、密钥
响应的统一处理
封装请求方法
全局配置
经常用的配置
baseURL
:baseURL
将自动加在 url
前面,除非 url
是一个绝对 URL
timeout
: timeout
指定请求超时的毫秒数, 如果请求时间超过 timeout 的值,则请求会被中断
responseType
:表示浏览器将要响应的数据类型 , 选项包括: 'arraybuffer', 'document', 'json', 'text', 'stream'
haeaders
:自定义请求头
withCredentials
: 表示跨域请求时是否需要使用凭证
ts
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加密一下
出于权限和安全考虑可能需要密钥
把他们在请求拦截器中设置到请求头
ts
//使用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))
})
响应的统一基本处理,针对于错误的情况做统一全局处理
ts
//使用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))
})
封装请求方法
把对接口的请求封装为方法
js
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 实例和相关方法封装在一个类中,可以更清晰地组织代码,提高代码的可维护性和可读性,更好导出使用。
类可以实例化
为对象,每个对象都有自己的状态和行为
。这使得在应用程序中可以轻松地创建多个独立的客户端实例,并在它们之间共享或隔离状态。
js
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还存在就报错就行
这个功能有很多应用
js
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 中的内置对象,用于控制异步操作的中止,也用于取消正在进行的网络请求
js
//封装
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类中加入代码
js
//在请求拦截器加上
abortAxios.addPending(config)
//在响应拦截器加上
res && abortAxios.removePending(res.config)
接下来我们实现下超时重发
超时重发的意思是全局配置timeout配置后,如果请求超时会自动重新发送
那么如何知道请求超时错误呢
响应拦截器中错误err.message中有错误信息
error.message.indexOf("timeout") !== -1
使用这个来判断超时错误
实现起来也很简单 设置等待时间和重新发新发送次数再用Promise.then加定时器
让请求在发送一次
在响应拦截器中的错误
js
//在响应拦截器中
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版本就是把上述代码加了类型规范
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)
文章到这里就结束了,希望对你有所帮助