vue判断请求时间超过5秒显示关闭_正确使用Axios,提升接口请求幸福感
1 前景介绍 根据Axios中文文档:Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。因其实在是过于简单粗暴,好用的一逼,可以算是目前最流行的前后端数据交互的工具,前端人员基本上每天都在接触它。为达到体感更佳的使用效果,一般不会直接原生使用(当然你硬要原生使用也可以),一般需要做几个简单的二次封装动作。本文旨在通过在vue项目中对axios...
1 前景介绍
根据Axios中文文档:Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。因其实在是过于简单粗暴,好用的一逼,可以算是目前最流行的前后端数据交互的工具,前端人员基本上每天都在接触它。为达到体感更佳的使用效果,一般不会直接原生使用(当然你硬要原生使用也可以),一般需要做几个简单的二次封装动作。本文旨在通过在vue项目中对axios的二次封装使用做一个笔记,分享一下aiox封装中需要关注的几个点。
项目代码文件夹大概如下:
需要本文前需要说明的是:
- http文件夹下的
index.js
文件是对axios
封装的文件(本文重点关注讨论这个文件),api文件夹下的文件是使用http接口定义的文件,userStorage.js
是跟token和用户信息缓存相关的工具文件。- 本项目用的是
vant
UI框架,所以,跟UI相关的(比如Toast
),都是导入vant
中的,实际可根据具体使用UI框架替换就好(比如用的antd
)。- 虽然是vue项目中封装,但是react项目同理。
2 创建axios实例
创建一个axios实例非常简单,调用create()
方法就好了:
const http = axios.create({
//withCredentials: true, //表示跨域请求时是否需要使用凭证,可根据情况自己设定,我这儿用不上
timeout: 30000, //设置请求超时时间,单位:ms
//baseURL:'' //http请求域名,也可以在请求拦截器里面设置
});
3 请求拦截器封装
在我看来,请求拦截器的作用主要有两个:
1、对HTTP请求的Request Headers参数进行全局设置
2、设置接口请求的域名和接口路径,也就是baseURL和url
其实现代码片段如下:
http.interceptors.request.use(
(config) => {
// config中包含url就是后端给的接口路径
let { url } = config;
// 此处我的开发环境中因为使用了webpack的devServer代理,所以接口域名为空
let baseURL = '';
// 生产环境(build之后)的域名是直接从浏览器地址栏截取的
// 所以做出以下判断,可根据实际情况自行判断不同环境的域名
const isProd = process.env.NODE_ENV === 'production';
if (isProd) {
baseURL = `${document.location.protocol}//${document.location.host}/app`;
}
config = {
...config,
baseURL: baseURL,
url: url,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json; charset=utf-8',
...config.headers,
accessToken: UserStorage.getUaaTokenInfo().accessToken || '', //从缓存中取出登录获取的token
authType: 'account',
},
};
return config;
},
(error) => {
return Promise.reject(error);
}
);
最终在network中看到的Request Headers效果如下:
4 响应拦截器封装
响应拦截器的作用主要是以下两点
1、统一处理接口请求返回数据的后,再返回具体给业务层使用
2、统一处理接口异常状态码情况,统一显示错误信息
必须说明的是,这块处理的逻辑其实比较灵活,具体要看接口返回的数据是什么格式,根据具体情况来处理,达到以上目的。因为我们接口返回的restful风格,返回的接口格式如下图:
所以,如果你的接口返回跟我的这个类似,那么可以参考如下代码片段:
http.interceptors.response.use(
(res) => {
// console.log(res);
if (res.status === 200) {
if (res.data) {
const resStatus = res.data.status;
const msg = res.data.message || '未知错误';
switch (resStatus) {
case 200:
// 请求成功,返回正确的数据结果,其他情况都是返回错误信息
return res.data.data;
case 401: //登录过期,目前直接跳出系统
// 清空了缓存token等
UserStorage.clearLogin();
Toast(res.data.message || '登录过期,请重新登录');
// 清空了缓存之后,刷新直接跳到登录页(需要在路由的地方专门处理)
window.location.reload();
return;
case 500:
break;
}
Toast(msg);
return Promise.reject(msg);
}
return Promise.reject('未知错误');
}
return Promise.reject(res);
},
(error) => {
const status =
(error.response &&
error.response.status &&
error.response.status) ||
'';
const data = (error.response && error.response.data) || {};
if (data.message) {
Toast(data.message);
return Promise.reject(data.message);
}
if (
error.code == 'ECONNABORTED' &&
error.message.indexOf('timeout') != -1
) {
Toast('请求超时~~');
return Promise.reject('请求超时~~');
}
if (status === 401) {
Toast('登录过期,请重新登录');
return Promise.reject('登录过期,请重新登录');
}
if (status === 404) {
Toast('接口404报错');
return Promise.reject('接口404报错');
}
if (status === 500) {
console.log(error.response);
Toast('服务器错误');
return Promise.reject('接口404报错');
}
return Promise.reject('未知错误');
}
);
- 代码中的
Toast
组件是引用的vant
组件,实际根据项目使用的UI框架替换即可。- 只有请求成功(
status === 200
),返回正常的数据,请求失败或者异常,返回相关报错信息
5 全局loading效果封装
经过以上几步,一个二次封装的axios模块其实已经可以使用,这步操作的目的是由于本人比较懒,不想每次请求接口时加loading效果,所以在axios封装模块中实现全局统一封装了loading效果,即实现请求发出,页面就会自动加上loading效果。大概如下图所示:
这块的实现参考了这篇文章,在此感谢原作者。
- 首先在文件中加上显示loading和隐藏loading的方法,以下代码片段:
//loading对象
let loading = null;
//当前正在请求的数量
let needLoadingRequestCount = 0;
//显示loading
function showLoading(target) {
// 后面这个判断很重要,因为关闭时加了抖动,此时loading对象可能还存在,
// 但needLoadingRequestCount已经变成0.避免这种情况下会重新创建个loading
if (needLoadingRequestCount === 0 && !loading) {
loading = Toast.loading({
getContainer: target || 'body',
});
}
needLoadingRequestCount++;
}
//隐藏loading
function hideLoading() {
needLoadingRequestCount--;
needLoadingRequestCount = Math.max(needLoadingRequestCount, 0); //做个保护
if (needLoadingRequestCount === 0) {
//关闭loading
toHideLoading();
}
}
//防抖:将 300ms 间隔内的关闭 loading 合并为一次。防止连续请求时, loading闪烁的问题。
let toHideLoading = _.debounce(() => {
loading && loading.clear();
loading = null;
}, 300);
- 改造请求拦截器,接口请求前显示loading,异常情况时隐藏loading,加入代码片段如下:
http.interceptors.request.use(
(config) => {
//其他代码....
//判断当前请求是否设置了不显示Loading
if (config.headers.showLoading !== false) {
showLoading(config.headers.loadingTarget);
}
return config;
},
(error) => {
//判断当前请求是否设置了不显示Loading
if (error.config.headers.showLoading !== false) {
hideLoading();
}
return Promise.reject(error);
}
);
- 改造响应拦截器,接口请求完成后或异常的时候隐藏loading,加入代码片段如下:
http.interceptors.response.use(
(res) => {
//判断当前请求是否设置了不显示Loading(不显示自然无需隐藏)
if (res.config.headers.showLoading !== false) {
hideLoading();
}
// 其他代码...
},
(error) => {
//判断当前请求是否设置了不显示Loading(不显示自然无需隐藏)
if (error.config.headers.showLoading !== false) {
hideLoading();
}
// 其他代码...
}
);
以上代码都比较简单,阅读即懂,无需赘述。
6 使用说明
经过以上步骤,封装已经完成,一般是在api
目录下的文件中导入使用即可。
使用过程中几个关键点:
- 引入封装好的axios:
import http from '../http';
- 发送get请求:
// 演示发送get请求的例子
export function getRequestApi() {
return http.get(`/api/xxx`);
}
- 发送post请求:
// 演示发送post请求的例子
export function postRequestApi() {
return http.get(`/api/xxx`);
}
- 不想某个接口请求过程中显示loading:(结合第5点来看)
export function hideLoadingApi() {
return http.get(`/api/xxx`,{headers: {'showLoading': false}});
}
- 从某个页面发出请求的例子:
async getCategoryList() {
try {
// 注意要导入getRequestApi方法
const res = await getRequestApi();
if (res) {
//请求成功...
}
} catch (error) {
// 请求异常,这儿可能收到的是相应拦截器中返回的错误信息
console.log(error);
}
},
7 总结
- axios二次封装主要针对请求和想要拦截器封装
- 优良的项目代码组织以便获得更好的维护体感
- 本文提供的是一种满足常用的封装思路,复杂情况可能需要考虑:取消请求,不要重复提交同一个请求,断网提示等
以下源代码直接放出:
- http-->
index.js
import axios from 'axios';
import _ from 'lodash';
import { Toast } from 'vant';
import UserStorage from '../utils/UserStorage';
const http = axios.create({
//withCredentials: true, //表示跨域请求时是否需要使用凭证,可根据情况自己设定,我这儿用不上
timeout: 30000, //设置请求超时时间,单位:ms
//baseURL:''//http请域名,也可以在请求拦截器里面搞
});
//loading对象
let loading = null;
//当前正在请求的数量
let needLoadingRequestCount = 0;
//显示loading
function showLoading(target) {
// 后面这个判断很重要,因为关闭时加了抖动,此时loading对象可能还存在,
// 但needLoadingRequestCount已经变成0.避免这种情况下会重新创建个loading
if (needLoadingRequestCount === 0 && !loading) {
loading = Toast.loading({
getContainer: target || 'body',
});
}
needLoadingRequestCount++;
}
//隐藏loading
function hideLoading() {
needLoadingRequestCount--;
needLoadingRequestCount = Math.max(needLoadingRequestCount, 0); //做个保护
if (needLoadingRequestCount === 0) {
//关闭loading
toHideLoading();
}
}
//防抖:将 300ms 间隔内的关闭 loading 合并为一次。防止连续请求时, loading闪烁的问题。
let toHideLoading = _.debounce(() => {
loading && loading.clear();
loading = null;
}, 300);
http.interceptors.request.use(
(config) => {
// config中包含url就是后端给的接口路径
let { url } = config;
// 此处我的开发环境中因为使用了webpack的devServer代理,所以接口域名为空
let baseURL = '';
// 生产环境(build之后)的域名是直接从浏览器地址栏截取的
// 所以做出以下判断,可根据实际情况自行判断不同环境的域名
const isProd = process.env.NODE_ENV === 'production';
if (isProd) {
baseURL = `${document.location.protocol}//${document.location.host}/integrated-app`;
}
config = {
...config,
baseURL: baseURL,
url: url,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json; charset=utf-8',
...config.headers,
accessToken: UserStorage.getUaaTokenInfo().accessToken || '',
authType: 'account',
},
};
// 判断当前请求是否设置了不显示Loading
if (config.headers.showLoading !== false) {
showLoading(config.headers.loadingTarget);
}
return config;
},
(error) => {
// 判断当前请求是否设置了不显示Loading
if (error.config.headers.showLoading !== false) {
hideLoading();
}
return Promise.reject(error);
}
);
http.interceptors.response.use(
(res) => {
//判断当前请求是否设置了不显示Loading(不显示自然无需隐藏)
if (res.config.headers.showLoading !== false) {
hideLoading();
}
// console.log(res);
if (res.status === 200) {
if (res.data) {
const resStatus = res.data.status;
const msg = res.data.message || '未知错误';
switch (resStatus) {
case 200:
// 请求成功,返回正确的数据结果,其他情况都是返回错误信息
return res.data.data;
case 401: //登录过期,目前直接跳出系统
// 清空了缓存token等
UserStorage.clearLogin();
Toast('登录过期,请重新登录');
// 清空了缓存之后,刷新直接跳到登录页(需要在路由的地方专门处理)
window.location.reload();
return;
case 500:
break;
}
Toast(msg);
return Promise.reject(msg);
}
return Promise.reject('未知错误');
}
return Promise.reject(res);
},
(error) => {
//判断当前请求是否设置了不显示Loading(不显示自然无需隐藏)
if (error.config.headers.showLoading !== false) {
hideLoading();
}
const status =
(error.response &&
error.response.status &&
error.response.status) ||
'';
const data = (error.response && error.response.data) || {};
if (data.message) {
Toast(data.message);
return Promise.reject(data.message);
}
if (
error.code == 'ECONNABORTED' &&
error.message.indexOf('timeout') != -1
) {
Toast('请求超时~~');
return Promise.reject('请求超时~~');
}
if (status === 401) {
Toast('登录过期,请重新登录');
return Promise.reject('登录过期,请重新登录');
}
if (status === 404) {
Toast('接口404报错');
return Promise.reject('接口404报错');
}
if (status === 500) {
console.log(error.response);
Toast('服务器错误');
return Promise.reject('接口404报错');
}
return Promise.reject('未知错误');
}
);
export default http;
- utils-->
userStorage.js
class UserStorage {
//缓存登陆进入h5之后获取到的用户信息
static saveUserInfo(userInfo) {
sessionStorage.setItem('userInfo', JSON.stringify(userInfo));
}
//获取缓存中的用户信息
static getUserInfo() {
const resUser = sessionStorage.getItem('userInfo');
if (resUser) {
return JSON.parse(resUser);
}
return {};
}
//缓存获取的用户token信息
static saveUaaTokenInfo(tokenInfo) {
sessionStorage.setItem('uaaToken', JSON.stringify(tokenInfo));
}
//获取缓存中的用户token
static getUaaTokenInfo() {
const tokenInfo = sessionStorage.getItem('uaaToken');
if (tokenInfo) {
return JSON.parse(tokenInfo);
}
return {};
}
static clearLogin() {
sessionStorage.clear();
}
}
export default UserStorage;
更多推荐
所有评论(0)