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


    前言

    搭建项目时,每个项目都会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,
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在请求拦截器中加入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))
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

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

    //使用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))
    
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    封装请求方法

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

    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'})
    }
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    基本封装四大项思路已经完成

    我们可以用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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170

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

    原理很简单,定义一个数据结构存储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
    					}
    				})
    			}
    		)
    		)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    不过有更成熟的实现,其核心是通过 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    在刚刚RequestHttp类中加入代码
    //在请求拦截器加上
    abortAxios.addPending(config)
    //在响应拦截器加上
    res && abortAxios.removePending(res.config)
    
    • 1
    • 2
    • 3
    • 4

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

    超时重发的意思是全局配置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));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    最终实现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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182

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

  • 相关阅读:
    Mysql索引、事务与存储引擎 (事务、MySQL 存储引擎)
    数据血缘全方位理解&实施指南
    图片采集下载
    Linux——文件传输协议知识点梳理
    输入输出管理:I/O控制方式
    机试(cs,se)
    DFT compiler极简示例2(使用autofix)
    java计算机毕业设计学生就业创业管理系统源代码+系统+数据库+lw文档
    BCC源码内容概览(2)
    MySQL的JDBC 编程
  • 原文地址:https://blog.csdn.net/weixin_63625059/article/details/138184933