首页 > 其他分享 >vue3+ts Axios封装—重复请求拦截

vue3+ts Axios封装—重复请求拦截

时间:2023-09-04 14:25:37浏览次数:57  
标签:Axios const 请求 vue3 ts key error return config

创建好vue3项目

1.安装Axios与Element Plus

Axios安装

npm install axios

Element Plus 安装

官网入口:https://element-plus.gitee.io/zh-CN/

npm install element-plus --save

Element 主要用到信息提示 与 全屏加载动画

2.在src 目录下创建 api 文件夹和 utils 文件夹

api 文件夹下 封装 Axios封装 与 请求配置

utils 文件夹下 operate.ts 配置接口地址 与其他全局ts

 

3.Axios封装

旧版本地址:https://www.cnblogs.com/lovejielive/p/16363587.html

新版本:主要增加动态控制是否显示加载动画。

是否需要判断重复请求。

优化请求接口配置参数写法。

扩展AxiosRequestConfig 增加自定义参数

declare module 'axios' {
    //请求自定义参数
    interface AxiosRequestConfig {
        // 是否显示加载框
        ifLoading?: boolean
        // 是否允许重复请求
        repeatRequest?: boolean
        // 登录 token
        isToken?: any;
    }
}

3.1重复请求判断

通过配置repeatRequest是否允许重复请求,来开启判断。主要在api.ts中配置。

每一次请求创建一个key,判断是否存在,如存在执行.abort()取消当前请求,

不存在pendingMap中新增一个key。

通过AbortController来进行手动取消。

主要代码

//格式化请求链接
function getRequestKey(config: AxiosRequestConfig) {
    const { url, method, data, params } = config,
        //字符串化参数
        dataStr = JSON.stringify(data) || '',
        paramsStr = JSON.stringify(params) || '',
        //记得这里一定要处理 每次请求都掉会变化的参数(比如每个请求都携带了时间戳),否则二个请求的key不一样
        key = [method, url, dataStr, paramsStr].join("&");
    return key;
}

//创建存储 key 的 集合
const pendingMap = new Map()

//是否重复请求key
function setPendingMap(config: AxiosRequestConfig) {
    //手动取消
    const controller = new AbortController()
    config.signal = controller.signal
    const key = getRequestKey(config)
    //判断是否存在key 存在取消请求 不存在添加
    if (pendingMap.has(key)) {
        // abort取消请求
        pendingMap.get(key).abort()
        //删除key
        pendingMap.delete(key)
    } else {
        pendingMap.set(key, controller)
    }
}

在接口消息提示时,通过 axios.isCancel(error) 过滤掉已取消的请求,

//拦截掉重复请求的错误,中断promise执行
if (axios.isCancel(error)) return []

3.2 axios完整代码

api文件夹下 创建 request-wrapper.ts Axios封装
/*
 * @description: 请求封装
 * @Author: Jay
 * @Date: 2023-04-11 13:24:41
 * @LastEditors: Jay
 * @LastEditTime: 2023-09-04 11:49:10
 */

// 导入axios
import axios, { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
// 使用element-ui ElMessage做消息提醒  ElLoading加载
import { ElMessage, ElLoading } from "element-plus";
//请求头
import operate from "@/utils/operate"

//加载配置
let loadingInstance: { close: () => void };
let requestNum = 0;
//加载动画
const addLoading = () => {
    // 防止重复弹出
    requestNum++;
    if (requestNum === 1) {
        loadingInstance = ElLoading.service({ fullscreen: true });
    }
}
// 关闭 加载动画
const cancelLoading = () => {
    requestNum--;
    if (requestNum === 0) loadingInstance?.close();
}

//格式化请求链接
function getRequestKey(config: AxiosRequestConfig) {
    const { url, method, data, params } = config,
        //字符串化参数
        dataStr = JSON.stringify(data) || '',
        paramsStr = JSON.stringify(params) || '',
        //记得这里一定要处理 每次请求都掉会变化的参数(比如每个请求都携带了时间戳),否则二个请求的key不一样
        key = [method, url, dataStr, paramsStr].join("&");
    return key;
}

//创建存储 key 的 集合
const pendingMap = new Map()

//是否重复请求key
function setPendingMap(config: AxiosRequestConfig) {
    //手动取消
    const controller = new AbortController()
    config.signal = controller.signal
    const key = getRequestKey(config)
    //判断是否存在key 存在取消请求 不存在添加
    if (pendingMap.has(key)) {
        // abort取消请求
        pendingMap.get(key).abort()
        //删除key
        pendingMap.delete(key)
    } else {
        pendingMap.set(key, controller)
    }
}

//增加新的请求参数类型
declare module 'axios' {
    //请求自定义参数
    interface AxiosRequestConfig {
        // 是否显示加载框
        ifLoading?: boolean
        // 是否允许重复请求
        repeatRequest?: boolean
        // 登录 token
        isToken?: any;
    }

    // 解決 类型“AxiosResponse<any, any>”上不存在属性“code”
    // interface AxiosResponse<T = any> {
    //     // 请求 data 里的一级参数
    //     code: number;
    //     time: string;
    //     msg: string;
    //     data: T;
    // }
}

//创建axios的一个实例
const axiosInstance: AxiosInstance = axios.create({
    //接口统一域名
    baseURL: operate.baseUrl(),
    //设置超时
    timeout: 1000 * 30,
    //跨域携带cookie
    withCredentials: true,
})

// 添加请求拦截器
axiosInstance.interceptors.request.use(
    (config) => {
        //加载动画
        if (config?.ifLoading) addLoading();
        //是否判断重复请求
        if (!config.repeatRequest) {
            setPendingMap(config)
        }

        //判断是否有token 根据自己的需求判断
        const token = config.isToken
        console.log("判断是否有token", token)
        if (token != undefined) {
            //如果要求携带在参数中
            config.params = Object.assign({}, config.params, token)
            // 如果要求携带在请求头中
            // config.headers = Object.assign({}, config.headers, operate.uploadParameters())
        }
        return config
    },
    (error: AxiosError) => {
        return Promise.reject(error)
    }
)

// 添加响应拦截器
axiosInstance.interceptors.response.use((response: AxiosResponse) => {
    const config = response.config
    // 关闭加载 动画
    if (config?.ifLoading) cancelLoading();
    //是否登录过期
    if (response.data.code == 400 || response.data.code == 401) {
        ElMessage.error("登录过期,请重新登录")
        // //清除登录缓存
        // store.commit("LOGOUT")
        // //返回首页
        // setTimeout(() => {
        //     router.push("/");
        // }, 500);
        return
    }
    // 返回参数
    return response.data
})

// 错误处理
axiosInstance.interceptors.response.use(undefined, (error) => {
    const config = error.config
    // 关闭加载 动画
    if (config?.ifLoading) cancelLoading();

    //拦截掉重复请求的错误,中断promise执行
    if (axios.isCancel(error)) return []

    /***** 接收到异常响应的处理开始 *****/
    if (error && error.response) {
        // 1.公共错误处理
        // 2.根据响应码具体处理
        switch (error.response.status) {
            case 400:
                error.message = '错误请求'
                break;
            case 401:
                error.message = '未授权,请重新登录'
                break;
            case 403:
                error.message = '拒绝访问'
                break;
            case 404:
                error.message = '请求错误,未找到该资源'
                // window.location.href = "/NotFound"
                break;
            case 405:
                error.message = '请求方法未允许'
                break;
            case 408:
                error.message = '请求超时'
                break;
            case 500:
                error.message = '服务器端出错'
                break;
            case 501:
                error.message = '网络未实现'
                break;
            case 502:
                error.message = '网络错误'
                break;
            case 503:
                error.message = '服务不可用'
                break;
            case 504:
                error.message = '网络超时'
                break;
            case 505:
                error.message = 'http版本不支持该请求'
                break;
            default:
                error.message = `连接错误${error.response.status}`
        }
    } else {
        // 超时处理
        if (JSON.stringify(error).includes('timeout')) {
            error.message = '服务器响应超时,请刷新当前页'
        } else {
            error.message = '连接服务器失败'
        }
    }

    //提示
    ElMessage.error(error.message)

    /***** 处理结束 *****/
    return Promise.resolve(error)
})

export default axiosInstance
request-wrapper.ts
api文件夹下 创建 api.ts 接口配置
/*
 * @description: 请求接口 配置
 * @Author: Jay
 * @Date: 2023-04-11 13:24:41
 * @LastEditors: Jay
 * @LastEditTime: 2023-09-04 13:30:09
 */

//导入 Axios 请求
import request from '@/utils/request'
//其他配置
import operate from '@/utils/operate';

// 官网接口
export const homePost = (data?: any) => {
    return request({
        url: '/api/index',
        method: 'post',
        data,
        //登录token
        isToken: operate.isToken(),
        //加载动画是否启动
        ifLoading: true,
        //是否允许重复请求
        repeatRequest: false,
    })
}

/*
请求配置与使用

* 请求 方式
    export const 名字 = (data: any) =>
        request.post("接口", data, {
            直接为空
            注:只能配置 AxiosRequestConfig 里有的参数名 可不用配置
        });

*使用 方法
   *引入
        import {
            名字
        } from "../api/api"
    *生命周期中 请求
        名字({请求参数}).then((res) => {
            console.log(res)
        })
*/
api.ts
开始请求
<script lang="ts" setup>
import { onMounted } from "vue";
import { homePost } from "@/api/api";

//生命周期
onMounted(() => {
  homePost().then((res) => {
    console.log("第一次", res);
  });
  homePost().then((res) => {
    console.log("第二次", res);
  });
});
</script>

请求结果

第一次请求被拦截,只有第二次成功返回

3.3 operate.ts 方法

主要放置一些 全局参数与方法。

在页面中可以通过 import operate from "@/utils/operate" 导入使用,也可以在main.ts中全局配置。

/*
 * @description: 全局js
 * @Author: Jay
 * @Date: 2023-09-04 13:53:47
 * @LastEditors: Jay
 * @LastEditTime: 2023-09-04 13:55:44
 */

// vuex 数据
import store from '../store/index'

//接口地址
const baseUrl = () => {
    if (process.env.NODE_ENV == "development") {
        //开发环境
        return "";
    } else {
        //正式环境
        return "";
    }
}

//获取用户token
const isToken = () => {
    if (store.state.Authorization != '') {
        return store.state.Authorization
    }
    return '';
}

/* eslint-disable */

/*
    格式化时间 加上时分秒
    num: 后台时间格式
    type: 'YY-MM-DD' 年月日 ,'HH-MM-SS' 时分秒 ,不传 年月日时分秒
*/
const happenTime = (num: any, type: string) => {
    let date = new Date(num * 1000);
    //时间戳为10位需*1000,时间戳为13位的话不需乘1000
    let y: any = date.getFullYear();
    let MM: any = date.getMonth() + 1;
    MM = MM < 10 ? ('0' + MM) : MM; //月补0
    let d: any = date.getDate();
    d = d < 10 ? ('0' + d) : d; //天补0
    let h: any = date.getHours();
    h = h < 10 ? ('0' + h) : h; //小时补0
    let m: any = date.getMinutes();
    m = m < 10 ? ('0' + m) : m; //分钟补0
    let s: any = date.getSeconds();
    s = s < 10 ? ('0' + s) : s; //秒补0
    if (type === 'YY-MM-DD') {
        //年月日
        return y + '-' + MM + '-' + d;
    } else if (type === 'HH-MM-SS') {
        //时分秒
        return h + ':' + m + ':' + s;
    } else {
        //全部
        return y + '-' + MM + '-' + d + ' ' + h + ':' + m + ':' + s;
    }
}
/* eslint-enable */


// 页面回到顶部(滚动效果)
/*
使用方法
 //监听滚动事件
  window.addEventListener("scroll", proxy.$operate.handleScroll, {
    once: true,
  });
*/
const handleScroll = () => {
    let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
    console.log(scrollTop, "scrollTop");
    if (scrollTop > 0) {
        const timeTop = setInterval(() => {
            document.documentElement.scrollTop = document.body.scrollTop = scrollTop -= 50; //一次减50往上滑动
            if (scrollTop <= 0) {
                clearInterval(timeTop);
            }
        }, 10); //定时调用函数使其更顺滑
    }
};

export default {
    baseUrl,
    isToken,
    happenTime,
    handleScroll
}
operate.ts

 

标签:Axios,const,请求,vue3,ts,key,error,return,config
From: https://www.cnblogs.com/lovejielive/p/17676856.html

相关文章

  • vue3+vite使用require引用图片失效问题
    首先,这个问题的原因跟vue无关,是vite引用只支持import,require是隔壁webpack的引用方式,vite用自身的url可以用import.meta.url来拼装项目路径,如下:这个原理只不过是在发布的地址上去找对应图片,而且只会找项目中public文件夹下的图片资源,assets文件夹下的图片资源找不到(原因是publ......
  • Apache IoTDB开发之Load External TsFile工具
    LoadExternalTsFile工具简介加载外部tsfile工具允许用户从正在运行的ApacheIoTDB实例加载tsfiles、删除tsfile或将tsfile移动到目标目录。用法用户通过Cli工具或JDBC向ApacheIoTDB系统发送指定的命令以使用该工具。加载TS文件加载tsfiles的命令是。load"<p......
  • vue3 使用vue-router 进行网页跳转以及获取问号后面的参数
    关键代码:constrouter=useRouter()constauthor='myname'router.push({name:'Edit',query:{author}})constroute=useRoute()constvalue=route.query.key详细步骤:0.Initialgitclonehttps://github.com/element-plus/element-plus-v......
  • ARTS打卡---第三周
    Algorithm力扣中等题:77. 组合 https://leetcode.cn/problems/combinations/解题思路:标准的DFS模版题,查看与回溯即可解决。Review未完成。Tiphttps://www.cnblogs.com/zhaozg/p/17675859.htmlShare暂无......
  • Failed to start bean 'documentationPluginsBootstrapper'; nested exception is jav
    2023-09-0322:53:53.622WARN20788---[main]ConfigServletWebServerApplicationContext:Exceptionencounteredduringcontextinitialization-cancellingrefreshattempt:org.springframework.context.ApplicationContextException:Failedtostartbean......
  • CF838D Airplane Arrangements 题解
    题意一架飞机有\(n\)个座位排成一列,有\(m\)名乘客(\(m\leqn\))依次上飞机。乘客会选择一个目标座位(两人可以选同一个目标座位),然后选择从前门或者后门上飞机,上飞机后,他们会走到自己的目标座位,如果目标座位已经有人坐了,他们会继续往前走,在走到第一个空位后坐下。如果走到最后......
  • 软件开发必读!华为云软件开发生产线CodeArts深度体验指南
    7月7日-9日,随着华为开发者大会2023的到来,华为云软件开发生产线CodeArts也走进了很多开发者的视野。开发者都比较好奇,什么是华为云CodeArts?CodeArts都有什么功能?在软件开发中能起到什么作用?CodeArtssnap是否有介绍的那般神奇?本文将为各位开发者详细介绍如何从了解学习到体验掌握华......
  • 封装Echarts组件 出现 多个Echarts组件只显示一个的问题
    代码部分:代码是没有问题的但是页面中只显示了一个ecahrts 解决:经百度后发现是组件中的Dom使用了固定id的问题 如果这么写那么页面中只会显示最后一个ecarts并且显示位置为第一个ecahrts组件的位置修改:  ok,解决! ......
  • UTSJSONOBJECT
    UTSJSONObjectUTSJSONObject是UTS语言的内置类型,主要用来操作匿名对象#创建实例UTSJSONObject对象的实例目前主要通过两种方式来创建:通过对象字面量constperson:UTSJSONObject={name:'Tom',printName:()=>{//...}}复制代码通过JS......
  • 图解Spark Graphx基于connectedComponents函数实现连通图底层原理
    原创/朱季谦第一次写这么长的graphx源码解读,还是比较晦涩,有较多不足之处,争取改进。一、连通图说明连通图是指图中的任意两个顶点之间都存在路径相连而组成的一个子图。用一个图来说明,例如,下面这个叫graph的大图里,存在两个连通图。左边是一个连接图,该子图里每个顶点都存在路......