完整的Axios封装-单独API管理层、参数序列化、取消重复请求、Loading、状态码...

article/2025/9/20 19:41:03

前言

Axios 相信对Vue熟悉的铁汁对它不会感到陌生了(当然不熟悉Vue你也可以认识它),这简直就是前端近年来的一大杀器,自从Vue2开始之后,官方推荐使用axios来进行网络请求,后面基本大部分Vue项目都能瞧见它的身影。

接下来我们就话不多说了,直接开始今天的主题,虽然axios很强,但是单纯的axios并不能满足我们日常的使用,因此很多时候我们都需要对axios进行二次封装,接下来我们就来详细讨论讨论。

准备工作

前端

Vite 一个超级超级超级快的开发神器,依旧是 尤雨溪 大大的杰作,这次就用vite来初始化项目来完成编码。

直接初始化项目,更多详情。

npm init @vitejs/app

下载axios依赖。

npm install axios

后端

借用node自个搭建一个简单的服务器,之所以自己弄个服务,不随便网上找个接口请求,也是为了后面方便验证一些特殊情况,比如请求超时、不同HTTP状态码、各种响应的数据结构等等。

在上面初始化好的项目目录下直接创建 service 目录,搭建服务的详情可以点这里(代码很简单,直接复制下面的router.js文件与app.js文件的代码即可)

在这里插入图片描述

之后我们通过命令 node app.js 启动服务,就能拥有三个这样子的接口:

  • http://localhost:8888/api/register
  • http://localhost:8888/api/login
  • http://localhost:8888/api/list

独立的API管理层

做好以上准备工作后,我们就可以开始进入正题了。一个项目的所有API接口统一管理是非常重要的,这样便于后期的更新维护,为此我们单独划分出API层来管理项目的所有API,以模块来划分每个API归属的文件。

我们在项目中创建 api文件夹 用来管理所有的API,创建 axios.js 文件二次封装axios,其他文件就是对应项目中的功能模块,如所有商品相关的API就放在 goods.js 文件,所有订单相关的API就放在 order.js 中,这样子就很有条理性。

在这里插入图片描述

一、我们先来简单的编写 axios.js

import axios from 'axios';function myAxios(axiosConfig) {const service = axios.create({baseURL: 'http://localhost:8888', // 设置统一的请求前缀timeout: 10000, // 设置统一的超时时长});return service(axiosConfig)
}export default myAxios;

需要注意的是 service(axiosConfig) 返回的是一个Promise对象哦。


(上面之所以设计成一个函数是为了后续的一些封装操作,之后会讲到)

二、下面我们来 goods.js 中编写获取商品列表的API。

import myAxios from './axios';export function getListAPI(paramsList) {return myAxios({url: '/api/list',method: 'get',})
}

如果请求是绝对路径,也可以直接填入url参数中,baseUrl 参数不并会再加上个前缀,这是baseUrl参数的特性哦。

三、最后我们在页面中具体使用,在 App.vue 文件中随便加一个按钮,点击触发请求。

<template><button @click="getList">点击</button>
</template><script lang='ts'>
import {defineComponent} from 'vue'
import {getListAPI} from '@/api/goods.js';
export default defineComponent({setup() { function getList() {getListAPI().then(res => {console.log(res)})}return {getList}}
})
</script>

到此,我们就简单的划分出 API 管理层了,这样我们每次新增加一个 API,只需要找到对应模块的 API 文件去添加即可,然后再到具体页面导入使用就行啦。

你可以用 xxxAPI 结尾的形式来命名 API 方法,这样子可以和普通方法区别出来,既明确又不用再为取名而烦恼了,可谓一举两得呢。(ω)

可能很多小伙伴会觉得这样子每次都需要去导入,会很麻烦,现在网上有很多做法就是直接将所有的 API 都挂载在 Vue 的实例上,直接通过 this.$axios.getList() 这样子去使用。个人感觉这样确实挺方便的,但是,它比较适合项目小、API 比较少的情况,要是项目比较庞大,里面的 API 比较多,就容易开始混淆,不好分类 API,使用 this 也还要考虑 this 指向问题,Vue3 更是没有所谓的 this

POST请求参数序列化

在POST请求中的 Content-Type 常见的有以下3种形式:

  • Content-Type: application/json
  • Content-Type: application/x-www-form-urlencoded
  • Content-Type: multipart/form-data

现在主流基本在用application/json形式,Axios默认以这种形式工作,我们给后端接口传递参数也简单,直接丢在其data参数就行了。


我们 user.js 文件中编写登录API

import myAxios from './axios';export function login(paramsList) {return myAxios({url: '/api/login',method: 'post',data: paramsList});
}

在具体页面导入调用该方法传递相关参数即可。

但是有时候后端要求Content-Type必须以application/x-www-form-urlencoded形式,那么通过上面传递的参数,后端是收不到的,我们必须对参数数据进行所谓的序列化处理才行,让它以普通表单形式(键值对)发送到后端,而不是json形式,更多关于序列化内容就自行百度啦,这里就告诉你如何做就行啦。

// user.js
import myAxios from './axios';export function loginAPI(paramsList) {return myAxios({url: '/api/login',method: 'post',data: paramsList,headers: {'Content-Type': 'application/x-www-form-urlencoded'},transformRequest: [(data) => {let result = ''for (let key in data) {result += encodeURIComponent(key) + '=' + encodeURIComponent(data[key]) + '&'}return result.slice(0, result.length - 1)}],});
}

我通过 headers 来指定Content-Type的形式,对于 transformRequest 就是允许在向服务器发送前,修改请求数据,但只能用在 ‘PUT’,‘POST’ 和 ‘PATCH’ 这几个请求方法,且后面数组中的函数必须返回一个字符串,或 ArrayBuffer,或 Stream,更多的还有 transformResponse 能在传递给 then/catch 前,允许修改响应数据,其余更多参数的可以去 Axios文档 查看。

(开始把Axios二次封装设计成一个函数,这里就很方便能单独配置单个请求不同的axios配置了,是不是很棒,但真正的方便是在后面自定义Loading的时候才更方便点哦,接着往下看咯)

最后通过浏览器network点击图中红框中的 view source就能看到序列化后的参数形式了。

在这里插入图片描述

在这里插入图片描述

用qs模块来序列化参数

我们也能通过第三方依赖来序列化参数,就更加方便简洁,下载qs模块。

npm install qs
// user.js
import qs from 'qs';
export function loginAPI(paramsList) {return myAxios({url: '/api/login',method: 'post',data: paramsList,headers: {'Content-Type': 'application/x-www-form-urlencoded'},transformRequest: [(data) => {return qs.stringify(data)}],});
}

取消重复请求

说起这个重复请求,感觉用到得比较少,心理总有怎么一种想法就算多请求一次又能怎么样,服务器会塌吗?页面会挂吗?明显不会嘛,不要大惊小怪,哈哈哈。再说没事怎么会多发重复的请求呢?不可能的。

在这里插入图片描述

而且做取消重复请求操作,其实取消后的请求还是有可能会到达了后端,只是前端浏览器不处理而已,但是呢,哎,我们还是得做做工作,不,非做不可,所谓以防万一,严谨,程序猿需要严谨!!!

发生重复请求的场景一般有这两个:

  • 快速连续点击一个按钮,如果这个按钮未进行控制,就会发出重复请求,假设该请求是生成订单,那么就有产生两张订单了,这是件可怕的事情。当然一般前端会对这个按钮进行状态处理控制,后端也会有一些幂等控制处理策略啥的,这是个假设场景,但也可能会发生的场景。
  • 对于列表数据,可能有tab状态栏的频繁切换查询,如果请求响应很慢,也会产生重复请求。当然现在很多列表都会做缓存,如Vue中用 <keep-alive />

如何取消一个已发送的请求

在开始正题前,我们要先来了解一下,如何取消一个已发送的请求,不知道铁汁们对JS中的 XMLHttpRequest 对象是否了解?(不知道也当你知道了) 你只要知道axios底层就是依赖于它的就行,也就是它的二次封装,那我们对axios再次封装,也就是三次封装?套娃?

XMLHttpRequest 对象是我们发起一个网络请求的根本,在它底下有怎么一个方法 .abort(),就是中断一个已被发出的请求。

在这里插入图片描述

那么axios自然也有对其的相关封装,就是 CancelToken,文档上介绍的用法:

var CancelToken = axios.CancelToken;
var cancel;axios.get('/user/12345', {cancelToken: new CancelToken(function executor(c) {// executor 函数接收一个 cancel 函数作为参数cancel = c;})
});// 取消请求
cancel();

简单理解就是通过 new axios.CancelToken()给每个请求带上一个专属的CancelToken,之后会接收到一个cancel() 取消方法,用于后续的取消动作,所以我们需要对应的存储好这个方法。

开始正题

通过上面的了解,下面就能进入正题部分了,接下来我们大致整体思路就是收集正在请求中接口,也就是接口状态还是pending状态的,让他们形成队列储存起来。如果相同接口再次被触发,则直接取消正在请求中的接口并从队列中删除,再重新发起请求并储存进队列中;如果接口返回结果,就从队列中删除,以此过程来操作。

判断重复请求并储存进队列

首先我们要收集请求中的接口并判断哪些请求是重复请求,我们才能取消它,那么如何判断呢?很简单,只要是请求地址、请求方式、请求参数一样,那么我们就能认为是一样的。而我们要存储的队列里面的数据结构很明显应该是以键值对的形式来存储,这里面我们选择 Map 对象来操作。

// axios.js
const pendingMap = new Map();/*** 生成每个请求唯一的键* @param {*} config * @returns string*/
function getPendingKey(config) {let {url, method, params, data} = config;if(typeof data === 'string') data = JSON.parse(data); // response里面返回的config.data是个字符串对象return [url, method, JSON.stringify(params), JSON.stringify(data)].join('&');
}/*** 储存每个请求唯一值, 也就是cancel()方法, 用于取消请求* @param {*} config */
function addPending(config) {const pendingKey = getPendingKey(config);config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) => {if (!pendingMap.has(pendingKey)) {pendingMap.set(pendingKey, cancel);}});
}

取消重复请求并出删除队列

// axios.js
/*** 删除重复的请求* @param {*} config */
function removePending(config) {const pendingKey = getPendingKey(config);if (pendingMap.has(pendingKey)) {const cancelToken = pendingMap.get(pendingKey);cancelToken(pendingKey);pendingMap.delete(pendingKey);}
}

添加拦截器

// axios.js
function myAxios(axiosConfig) {const service = axios.create({baseURL: 'http://localhost:8888', // 设置统一的请求前缀timeout: 10000, // 设置统一的超时时长});service.interceptors.request.use(config => {removePending(config);addPending(config);return config;}, error => {return Promise.reject(error);});service.interceptors.response.use(response => {removePending(response.config);return response;},error => {error.config && removePending(error.config);return Promise.reject(error);});return service(axiosConfig)
}

我们在上面提到的 getList() 方法里面简单模拟连续发出了三次重复请求,然后把浏览器设置成3G模式,就能看到效果啦。

// App.vue
function getList() {getListAPI().then(res => {console.log(res)})setTimeout(() => {getListAPI().then(res => {console.log(res)})}, 200);setTimeout(() => {getListAPI().then(res => {console.log(res)})}, 400);
}

在这里插入图片描述

需要注意,上面说了取消正在请求中的接口,说明这接口有可能已经到达后端了,只是后端响应慢,所以如果你的接口响应比较快的话,就很难看到效果;如果你是自己搭建的服务,只要通过接口返回时延时下就可以看到效果;又或者通过浏览器的network调整网络速度也可以哦。在这里插入图片描述

对于取消后的请求我们也应该有个合理的处理,不能就不管了,尽可能的达到代码可控的底部,它会被归类到异常里面,下面会说到(ω)。

配置化

之所以弄成配置化取消重复请求,是因为可能存在一些特殊变态的场景情况,是需要重复请求,如输入实时搜索、实时更新数据等,反正就是可能存在吧。( ̄y▽ ̄)~*

// axios.js
function myAxios(axiosConfig, customOptions) {const service = axios.create({baseURL: 'http://localhost:8888', // 设置统一的请求前缀timeout: 10000, // 设置统一的超时时长});// 自定义配置let custom_options = Object.assign({repeat_request_cancel: true, // 是否开启取消重复请求, 默认为 true}, customOptions);service.interceptors.request.use(config => {removePending(config);custom_options.repeat_request_cancel && addPending(config);return config;}, error => {return Promise.reject(error);});...
}

我们在上面增加了一个自定义配置的参数,现在每个API方法就能拥有两个参数,第一个参数传递的是axios原本的一些配置,第二个参数就是我们自己的一些自定义参数了,如我们定义 repeat_request_cancel 来控制是否开启取消重复请求的功能。后续更多功能,我们也能添加进其中,相当于可定制化每个API方法,是不是很棒!!!

// goods.js
export function getListAPI(paramsList) {return myAxios({url: '/api/list',method: 'get',params: paramsList}, {repeat_request_cancel: false})
}

Loading

异步数据是非常常见的场景,一个良好的Loading效果能很好的加强用户体验,也能让我们回避一些问题,如上面提到的重复请求,如果在发起了一个请求后立即就出现一个Loading层,那么用户就无法再次点击而造成重复多次请求了。

添加怎么一个功能我们需要考虑怎么三件事:

  • 同一时间内发起多个请求,我们只需要展示一个Loading层即可,不需要产生多个造成重复展示。
  • 同一时间内发起多个请求展示的Loading层以最后一个请求响应而关闭销毁。
  • 此功能依旧要进行可配置化处理。

废话不多说,我们直接以 ElementPlus 的Loading效果玩耍,具体查看代码相关注释。

// axios.js
const LoadingInstance = {_target: null, // 保存Loading实例_count: 0
};function myAxios(axiosConfig, customOptions) {const service = axios.create({baseURL: 'http://localhost:8888', // 设置统一的请求前缀timeout: 10000, // 设置统一的超时时长});// 自定义配置let custom_options = Object.assign({repeat_request_cancel: true, // 是否开启取消重复请求, 默认为 trueloading: false, // 是否开启loading层效果, 默认为false}, customOptions);service.interceptors.request.use(config => {removePending(config);custom_options.repeat_request_cancel && addPending(config); // 创建loading实例  if (custom_options.loading) {LoadingInstance._count++;if(LoadingInstance._count === 1) {LoadingInstance._target = ElLoading.service();}}return config;}, error => {return Promise.reject(error);});service.interceptors.response.use(response => {removePending(response.config);custom_options.loading && closeLoading(custom_options); // 关闭loadingreturn response;},error => {error.config && removePending(error.config);custom_options.loading && closeLoading(custom_options); // 关闭loadingreturn Promise.reject(error);});return service(axiosConfig)
}/*** 关闭Loading层实例* @param {*} _options */
function closeLoading(_options) {if(_options.loading && LoadingInstance._count > 0) LoadingInstance._count--;if(LoadingInstance._count === 0) {LoadingInstance._target.close();LoadingInstance._target = null;}
}

关于上面_count变量的作用还不明白的可以康康,明白的当我没说-.-,假如现在同时发起两个请求,两个请求同时打开了个Loading层,现在有一个请求结束了,关闭了loading层,但是另一个请求由于某些原因并没有结束,还在请求,造成的后果就是页面请求还没完成,loading层却关闭了,用户会以为页面加载完成了,结果页面不能正常运行,导致用户体验不好,所以增加了个变量来记录请求的次数。

当然如果你是杠精那么你又会想如果这个接口是个响应时间比较长,而且获取的数据其实并不影响页面的其他操作,那么一直有个Loading层反而是体验差了。还好我有Plan B,故设计上面的Loading层是个可配置的选项,对于这种情况的API可以选择不用这个页面级别的Loading层,转而自己去具体内使用元素级别的Loading效果更佳。

对于ElementPlus的Loading组件,它还有很多配置参数。

在这里插入图片描述

我们也能自定义处理掉,考虑到简洁单一点我们直接增加第三个参数。

// axios.js
function myAxios(axiosConfig, customOptions, loadingOptions) {service.interceptors.request.use(config => {...// 创建loading实例if (custom_options.loading) {LoadingInstance._count++;if(LoadingInstance._count === 1) {LoadingInstance._target = ElLoading.service(loadingOptions);}}return config;}, ...);...  
}

至此,我们就能给每个需要页面级Loading层的API方法定制不同的Loading层了。(是不是很棒,又很花里胡哨?哈哈哈)

// goods.js
export function getListAPI(paramsList) {return myAxios({url: '/api/list',method: 'get',params: paramsList}, {loading: true}, {text: '获取列表数据....'})
}

判断不同HTTP状态码

一个良好展示接口实时状态的提示信息是非常重要的,开发时方便前端人员定位问题,测试时方便测试人员通知对应人员,在一些复杂特殊场景给予用户提示引导。

在上正式代码前,我们先打印几种接口异常情况,测试代码如下:

// axios.js
function myAxios(axiosConfig, customOptions, loadingOptions) {...service.interceptors.response.use(...error => {...httpErrorStatusHandle(error); // 处理错误状态码return Promise.reject(error); // 错误继续返回给到具体页面});...
}/*** 处理异常* @param {*} error */
function httpErrorStatusHandle(error) {console.log('error: ', error);console.log('error.message: ', error.message);console.log('error.response: ', error.response);
}

需要注意上面打印的 error 本质是个对象来着,但控制台可能不是很明显的表示,底下还有很多属性是我们能用到的,文档也有说哦。

各种异常情况

后端抛错、客户端断网

这种情况一般接口整个就挂了,客户端断网了也会是这种情况,我们能通过 window.navigator.onLine 来判断是否断网了。

// app.js
app.get('/api/list', (req, res) => {// console.log(a);throw new Error('错误啦!!!');
});

在这里插入图片描述

请求超时

我们更改node服务延时响应来制造超时效果

// app.js
app.get('/api/list', (req, res) => {setTimeout(() => {res.end();},  150000)
});

在这里插入图片描述

3XX 重定向

在这里插入图片描述
在这里插入图片描述

4XX 客户端错误

在这里插入图片描述
在这里插入图片描述

5XX 服务端错误

在这里插入图片描述
在这里插入图片描述

// app.js
app.get('/api/list', (req, res) => {// res.statusCode = 302;// res.end('重定向');// res.statusCode = 400;// res.end('请求参数错误');res.statusCode = 500;res.end('服务器内部错误');
});

上面就大致列了一下常见的各种情况,下面我就直接上代码,也挺简单,只要接口错误,提示对应的错误信息就完了(==)。

具体编码

// axios.js
/*** 处理异常* @param {*} error */
function httpErrorStatusHandle(error) {// 处理被取消的请求if(axios.isCancel(error)) return console.error('请求的重复请求:' + error.message);let message = '';if (error && error.response) {switch(error.response.status) {case 302: message = '接口重定向了!';break;case 400: message = '参数不正确!';break;case 401: message = '您未登录,或者登录已经超时,请先登录!';break;case 403: message = '您没有权限操作!'; break;case 404: message = `请求地址出错: ${error.response.config.url}`; break; // 在正确域名下case 408: message = '请求超时!'; break;case 409: message = '系统已存在相同数据!'; break;case 500: message = '服务器内部错误!'; break;case 501: message = '服务未实现!'; break;case 502: message = '网关错误!'; break;case 503: message = '服务不可用!'; break;case 504: message = '服务暂时无法访问,请稍后再试!'; break;case 505: message = 'HTTP版本不受支持!'; break;default: message = '异常问题,请联系管理员!'; break}}if (error.message.includes('timeout')) message = '网络请求超时!';if (error.message.includes('Network')) message = window.navigator.onLine ? '服务端异常!' : '您断网了!';ElMessage({type: 'error',message})
}

编写这个处理异常的方法其实不难,但我们注意要处理一下上面讲过的取消重复请求的情况,取消后的请求也会进入这其中,我们简单的将重复请求的接口打印在控制台即可。

我们借助ElementPlus的Message组件来提示信息,具体提示文案可以自行更改或添加更多情况,也能用接口来定义这些信息,就看具体情况啦。

当然也我们把该功能配置化:

// axios.js
function myAxios(axiosConfig, customOptions, loadingOptions) {...// 自定义配置let custom_options = Object.assign({...error_message_show: true, // 是否开启接口错误信息展示,默认为true}, customOptions);service.interceptors.response.use(...error => {...custom_options.error_message_show && httpErrorStatusHandle(error); // 处理错误状态码return Promise.reject(error); // 错误继续返回给到具体页面});...
}

其他实用的小优化

请求自动携带token

这里比较简单就直接上代码了。

// axios.js
import {getTokenAUTH} from '@/utils/auth';
function myAxios(axiosConfig, customOptions, loadingOptions) {...service.interceptors.request.use(config => {...// 自动携带tokenif (getTokenAUTH() && typeof window !== "undefined") {config.headers.Authorization = getTokenAUTH();}return config;}, ...);...
}
// auth.js
const TOKEN_KEY = '__TOKEN';
export function getTokenAUTH() {return localStorage.getItem(TOKEN_KEY);
}

因为我的token是存在本地缓存里面,如果你的token存在store里面,就自行修改修改咯,不要告诉我这你都不会,那你就和那啥没什么区别了。typeof window !== "undefined" 主要是为了兼容ssr的环境情况。

在这里插入图片描述

简洁的数据响应结构

// axios.js
function myAxios(axiosConfig, customOptions, loadingOptions) {...  // 自定义配置let custom_options = Object.assign({repeat_request_cancel: true, // 是否开启取消重复请求, 默认为 trueloading: false, // 是否开启loading层效果, 默认为falsereduct_data_format: true, // 是否开启简洁的数据结构响应, 默认为true}, customOptions);...  service.interceptors.response.use(response => {removePending(response.config);custom_options.loading && closeLoading(custom_options); // 关闭loadingreturn custom_options.reduct_data_format ? response.data : response;},...);...  
}

在这里插入图片描述

axios默认返回的响应数据会帮我们包上一层数据,而真正后端返回的数据都在response.data里面,这样有时我们返问数据就要镶嵌很长访问下去,如果中间有一层断了,就容易引起报错了。故我们能设置返回简洁点的数据直接给到具体页面逻辑中,方便使用,通过 reduct_data_format 参数来控制配置。


当然,上面只是简单缩短axios最外层而已,如果你已经很明确且有公司有规定的响应数据结构,那你也能自行再次修改,看具体场景而定。

关于code的错误提示

这点根据要根据具体业务场景而定!!!


很多时候后端接口总有在除HTTP状态码的情况下再定义一个 code 参数决定当前接口是否是“正常”的,一般正常的时候code会等于0,我们先直接上代码再解释。

function myAxios(axiosConfig, customOptions, loadingOptions) {...let custom_options = Object.assign({...code_message_show: false, // 是否开启code不为0时的信息提示, 默认为false}, customOptions);service.interceptors.response.use(response => {...if(custom_options.code_message_show && response.data && response.data.code !== 0) {ElMessage({type: 'error',message: response.data.message})return Promise.reject(response.data); // code不等于0, 页面具体逻辑就不执行了}return custom_options.reduct_data_format ? response.data : response;},...);...
}

简单来说,就是在code不等于0的时候,我们就直接展示后端带来的提示语,当然这要前后端先商量好,固定好响应的数据结构,而具体到页面逻辑里面我们就只处理code等于0的时候那种正常情况。当然我们也通过配置化来设定这个功能,如果前后端定义好数据结构,就直接改了 code_message_show 默认值,就不用一个一个接口去开启,也是很方便的一个功能吧。

完整代码

最后给出 axios.js 完整的代码,肝了两天,写累了,希望对你有所帮助吧。

在这里插入图片描述

import axios from 'axios';
import { ElLoading, ElMessage } from 'element-plus';
import {getTokenAUTH} from '@/utils/auth';const pendingMap = new Map();const LoadingInstance = {_target: null,_count: 0
};function myAxios(axiosConfig, customOptions, loadingOptions) {const service = axios.create({baseURL: 'http://localhost:8888', // 设置统一的请求前缀timeout: 10000, // 设置统一的超时时长});// 自定义配置let custom_options = Object.assign({repeat_request_cancel: true, // 是否开启取消重复请求, 默认为 trueloading: false, // 是否开启loading层效果, 默认为falsereduct_data_format: true, // 是否开启简洁的数据结构响应, 默认为trueerror_message_show: true, // 是否开启接口错误信息展示,默认为truecode_message_show: false, // 是否开启code不为0时的信息提示, 默认为false}, customOptions);// 请求拦截service.interceptors.request.use(config => {removePending(config);custom_options.repeat_request_cancel && addPending(config); // 创建loading实例if (custom_options.loading) {LoadingInstance._count++;if(LoadingInstance._count === 1) {LoadingInstance._target = ElLoading.service(loadingOptions);}}// 自动携带tokenif (getTokenAUTH() && typeof window !== "undefined") {config.headers.Authorization = getTokenAUTH();}return config;}, error => {return Promise.reject(error);});// 响应拦截service.interceptors.response.use(response => {removePending(response.config);custom_options.loading && closeLoading(custom_options); // 关闭loadingif(custom_options.code_message_show && response.data && response.data.code !== 0) {ElMessage({type: 'error',message: response.data.message})return Promise.reject(response.data); // code不等于0, 页面具体逻辑就不执行了}return custom_options.reduct_data_format ? response.data : response;},error => {error.config && removePending(error.config);custom_options.loading && closeLoading(custom_options); // 关闭loadingcustom_options.error_message_show && httpErrorStatusHandle(error); // 处理错误状态码return Promise.reject(error); // 错误继续返回给到具体页面});return service(axiosConfig)
}export default myAxios;/*** 处理异常* @param {*} error */
function httpErrorStatusHandle(error) {// 处理被取消的请求if(axios.isCancel(error)) return console.error('请求的重复请求:' + error.message);let message = '';if (error && error.response) {switch(error.response.status) {case 302: message = '接口重定向了!';break;case 400: message = '参数不正确!';break;case 401: message = '您未登录,或者登录已经超时,请先登录!';break;case 403: message = '您没有权限操作!'; break;case 404: message = `请求地址出错: ${error.response.config.url}`; break; // 在正确域名下case 408: message = '请求超时!'; break;case 409: message = '系统已存在相同数据!'; break;case 500: message = '服务器内部错误!'; break;case 501: message = '服务未实现!'; break;case 502: message = '网关错误!'; break;case 503: message = '服务不可用!'; break;case 504: message = '服务暂时无法访问,请稍后再试!'; break;case 505: message = 'HTTP版本不受支持!'; break;default: message = '异常问题,请联系管理员!'; break}}if (error.message.includes('timeout')) message = '网络请求超时!';if (error.message.includes('Network')) message = window.navigator.onLine ? '服务端异常!' : '您断网了!';ElMessage({type: 'error',message})
}/*** 关闭Loading层实例* @param {*} _options */
function closeLoading(_options) {if(_options.loading && LoadingInstance._count > 0) LoadingInstance._count--;if(LoadingInstance._count === 0) {LoadingInstance._target.close();LoadingInstance._target = null;}
}/*** 储存每个请求的唯一cancel回调, 以此为标识* @param {*} config */
function addPending(config) {const pendingKey = getPendingKey(config);config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) => {if (!pendingMap.has(pendingKey)) {pendingMap.set(pendingKey, cancel);}});
}/*** 删除重复的请求* @param {*} config */
function removePending(config) {const pendingKey = getPendingKey(config);if (pendingMap.has(pendingKey)) {const cancelToken = pendingMap.get(pendingKey);// 如你不明白此处为什么需要传递pendingKey可以看文章下方的补丁解释cancelToken(pendingKey);pendingMap.delete(pendingKey);}
}/*** 生成唯一的每个请求的唯一key* @param {*} config * @returns */
function getPendingKey(config) {let {url, method, params, data} = config;if(typeof data === 'string') data = JSON.parse(data); // response里面返回的config.data是个字符串对象return [url, method, JSON.stringify(params), JSON.stringify(data)].join('&');
}

补丁

在完整源码中,有一个 removePending() 方法,里面有一行 cancelToken(pendingKey); 代码,很多倔友在问为什么需要传递 pendingKey 参数 ?这里做个统一回复哈。

首先,你不传这个参数是完全没有问题的,对代码、程序没有影响的。

其次,它是一个提升开发者好感的选项,咋说?不急听我细细道来。

我们先来看看 Axios 官方的一个例子:

在这里插入图片描述

按小编标的序号,快速看完例子你明白了些什么没有呢?没懂?不急,继续往下看!

cancel() 方法允许传递一个参数,在执行这个方法后,axios 会进入错误状态,我们可以通过手动添加 .catch() 来捕获它,或者通过拦截器的第二个参数来处理它,而且它的 .message 属性能获取 cancel() 传递的值。 (记住这个用法)

提个醒,pendingKey 是由请求路径、请求方法、请求参数组成的,例如:
http://juejin.cn&get&{a:1,b:2}&

而当我们执行了 cancelToken(pendingKey); 后,程序会进入到拦截器下图这个位置:

在这里插入图片描述

当我们允许执行 httpErrorStatusHandle() 方法的时候,方法里面会有一段处理代码:

function httpErrorStatusHandle(error) {// 处理被取消的请求if(axios.isCancel(error)) return console.error('请求的重复请求:' + error.message);...
}

这段代码的作用就是:在控制台中打印出被取消了的请求的信息,这能更好的帮助开发者定位那些请求是会造成重复请求的。

最后,放个效果图,当然你不传的话,提示信息这里只是会变成 undefined 而已,不会有什么影响。

在这里插入图片描述

稍微讲解得有点啰嗦,希望你能明白哈,还是不懂的话,欢迎评论区给我留言。(^▽^)



至此,本篇文章就写完啦,撒花撒花。

在这里插入图片描述

希望本文对你有所帮助,如有任何疑问,期待你的留言哦。

老样子,点赞+评论=你会了,收藏=你精通了。


http://chatgpt.dhexx.cn/article/L6kB78FD.shtml

相关文章

Ajax介绍和Axios基本使用

Ajax介绍 Ajax本身就是Asynchronous JavaScript And XML的缩写&#xff0c;直译为&#xff1a;异步的JavaScript和XML。 在实际应用中Ajax指的是&#xff1a;不刷新浏览器窗口&#xff0c;不做页面跳转&#xff0c;局部更新页面内容的技术。 『同步』和『异步』是一对相对的…

Axios使用详解

文章目录 一、Promise的使用1. 基本语法2. Promise三种状态3. Promise链式调用1. 基本写法2. 使用静态方法3. 直接返回 4. Promise.all5. Promise.race 二、Axios使用1. 安装并引入2. 发送请求3. config配置4. 响应结构5. 并发请求6. 全局配置6. 创建实例7. 实例方法8. 拦截器请…

Uncaught (in promise) Error: Network Error at e.exports (axios.js:8:6410) at d.onerror (axio

axios报错&#xff1a; 原因&#xff1a; 后端没有给跨域权限&#xff0c;后端需要设置允许跨域的响应头&#xff0c;即可解决。 解决&#xff1a; node端设置响应头&#xff0c;解决跨域问题 。

axios封装

不要过度封装&#xff01;封装一个最简单的Axios&#xff01; - 掘金 (juejin.cn) 1 初始化 axios 实例 通过 create 方法我们得到了一个 axios 的实例&#xff0c;该实例上有很多方法&#xff0c;比如拦截器等等。我们创建实例的时候可以配置一些基础设置&#xff0c;比如基础…

axios学习

文章目录 前言一、axios的理解和使用axios的特点axios的安装方式axios的基本配置 二、axios的基本使用1.axios四种常用请求方式2.四种方式的基本使用基本架构代码示例&#xff1a;1. GET请求2. POST请求3. PUT请求4. DELETE请求 3.axios的别名方式请求使用 前言 axios是一个基…

vue封装axios

(4条消息) Vue——axios的二次封装_前端杂货铺的博客-CSDN博客 1.下载axios依赖包 npm install axios2.在src目录下新建utils文件夹&#xff0c;在utils文件夹下新建request.js文件 3.request.js import axios from axios import { Message, MessageBox } from element-ui …

axios

SegmentFault 头条问答专栏讲堂职位活动 消息注册 登录 home javascriptphppythonjavamysqliosandroidnode.jshtml5linuxccss3gitgolangrubyvimdockermongodb 文 axios 中文文档 翻译 axiosjavascript farmerz 2月24日发布 2 推荐 45 收藏&#xff0c;3.2k 浏览 axios 版…

Axios介绍

Axios是专注于网络数据请求的库&#xff0c;相比于XMLHttpRequest对象。axios简单易用&#xff0c;相比于jQuery&#xff0c;axios更加轻量化&#xff0c;只专注于网络数据请求 引入外部js文件 <script src"https://unpkg.com/axios/dist/axios.min.js"><…

VUE的axios的详细介绍和用法

Vue中发送网络请求有非常多的方式, 那么, 在开发中, 我们该如何选择呢? 选择一: 传统的Ajax是基于XMLHttpRequest(XHR) 为什么不用它呢? 非常好解释, 配置和调用方式等非常混乱.编码起来看起来就非常蛋疼. 所以真实开发中很少直接使用, 而是使用jQuery-Ajax 选择二: 在前…

AJAX,Axio异步框架(对原生AJAX封装)。web分区

1.Ajax的理解 以前服务器里的数据&#xff0c;都是存在Servlet域里&#xff0c;然后发给JSP&#xff0c;来进行显示。 有了AJAX&#xff0c;可以和服务器通信。不需要JSP作页面。 可以在Servlet把数据发给浏览器&#xff0c;然后在HTML页面显示。 1.1 以前的方法 1.2 现在的方…

类方法和对象方法的区别

类方法和对象方法 1.类方法属于本类的方法&#xff0c;不会因创建对象的不同而改变&#xff0c;类方法随着类的字节码文件加载而加载&#xff1b; 2.对象方法属于当前类创建的某个对象&#xff0c;会随着创建对象的不同而改变。如下图所示代码&#xff1a;

python中什么叫类、什么叫对象_Python中的类和对象是什么

一、面向过程和面向对象 面向过程:根据业务逻辑从上到下写代码。 面向对象:将数据与函数绑定到一起,进行封装,这样能够更快速的开发程序,减少了重复代码的重写过程。 二、类和对象 1、类的概念 面向对象编程的2个非常重要的概念:类和对象是面向对象编程的核心。 在使用对…

类与对象的区别?

对于初学者来说&#xff0c;类与对象之间的关系的非常模糊不清的&#xff0c;在这里跟大家分享一下&#xff0c;让初学者有所帮助。 一、类的概念&#xff1a; 类是具有相同属性和服务的一组对象的集合。它为属于该类的所有对象提供了统一的抽象描述&#xff0c;其内部包括属性…

Java类和对象 详解(一)

一、面向对象简述 面向对象是一种现在最为流行的程序设计方法&#xff0c;几乎现在的所有应用都以面向对象为主了&#xff0c;最早的面向对象的概念实际上是由IBM提出的&#xff0c;在70年代的Smaltalk语言之中进行了应用&#xff0c;后来根据面向对象的设计思路&#xff0c;才…

C++类和对象详细总结

目录 目录 类与对象概念 什么是对象 什么是类 什么是方法&#xff1a; 自定义类型&#xff08;类的关键字&#xff1a;class&#xff09; 定义类的格式 封装 类的特性 访问权限以及访问限定符 struct 定义的类和class定义的类的区别&#xff1a; 小结 对象中包含了…

C++类和对象详解

类与对象上篇&#xff1a; 主要内容&#xff1a; 1.类和对象的区别。 2.类的定义。 3.类的访问限定符和封装 4.类的作用域 5.类的实例化&#xff08;用类类型创建对象&#xff09; 6.计算类对象的大小 7.this指针 C语言是面向过程的&#xff0c;关注的是过程&#xff0c;分析…

面向过程和面向对象区别

&#xff08;1&#xff09;从设计思路来看。 面向过程&#xff1a;程序设计的重点是分析解决问题的步骤&#xff0c;以及完成步骤的流程&#xff0c;是一种结构化自上而下的程序设计方法。面向对象&#xff1a;程序设计的重点是把构成问题的事物分解成对象&#xff0c;从局部着…

Java基础——类和对象

目录 一、类和对象的基本概念 二、类与对象的定义与使用 1.创建类的语法&#xff1a; 2. 创建具体的对象&#xff1a; 3.范例&#xff08;创建一个Person 类的对象&#xff09; 三、static关键字 &#xff08;一&#xff09;static修饰属性&#xff08;类属性、类变量&a…

类,对象,方法与函数的区别

面向对象&#xff08;Object oriented Programming&#xff0c;OOP)编程 的思想主要是针对大型软件设计而来的。面向对象编程将数据和操作数据相关的方法封装到对象中&#xff0c;组织代码和数据的方式更加接近人的思维&#xff0c;使程序的扩展性更强、可读性更好&#xff0c;…

C++的类和对象

目录 C面向对象的三大特性&#xff1a;封装、继承、多态 封装 构造函数和析构函数 构造函数的分类与调用 深拷贝与浅拷贝 类对象作为类成员 静态成员 成员变量和成员函数是分开存储的 const修饰成员函数 友元 运算符重载 继承 多态 C面向对象的三大特性&#xff1…