axios请求接口http_正确使用Axios,提升接口请求幸福感

1 前景介绍

根据Axios中文文档:Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。因其实在是过于简单粗暴,好用的一逼,可以算是目前最流行的前后端数据交互的工具,前端人员基本上每天都在接触它。为达到体感更佳的使用效果,一般不会直接原生使用(当然你硬要原生使用也可以),一般需要做几个简单的二次封装动作。本文旨在通过在vue项目中对axios的二次封装使用做一个笔记,分享一下aiox封装中需要关注的几个点。

「项目代码文件夹大概如下:」

「需要本文前需要说明的是:」❝http文件夹下的index.js文件是对axios封装的文件(本文重点关注讨论这个文件),api文件夹下的文件是使用http接口定义的文件,userStorage.js是跟token和用户信息缓存相关的工具文件。

本项目用的是vantUI框架,所以,跟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
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值