二次封装axios
序:
Axios 是一个基于 promise 的 HTTP 库,用于浏览器和node.js等http客户端,主要用来向后台发送各种业务请求和特殊处理,由于axios支持后台数据交互、拦截请求和响应、取消请求、超时设定、转换json、防御XSRF攻击等特性。
一个axios.js
较完整的代码
import axios from 'axios';
import { ElLoading, ElMessage } from 'element-plus';
// 请求自动携带token
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, // 是否开启取消重复请求, 默认为 true
loading: false, // 是否开启loading层效果, 默认为false
reduct_data_format: true, // 是否开启简洁的数据结构响应, 默认为true
error_message_show: true, // 是否开启接口错误信息展示,默认为true
code_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);
}
}
// 自动携带token
if (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); // 关闭loading
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;
},
error => {
error.config && removePending(error.config);
custom_options.loading && closeLoading(custom_options); // 关闭loading
custom_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('&');
}
流程分步
文件一般存放位置:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5bbfBxyM-1648830928159)(C:\Users\huawei\AppData\Roaming\Typora\typora-user-images\image-20220401110912102.png)]
1、引入组件
import axios from 'axios';
// 引用qs模块,用来序列化post类型的数据
import qs from 'axios';
import { Message } from 'element-ui';
import store from 'store';
2、设置请求超时和post请求头
通过axios.defaults.timeout可以设置默认的请求超时时间,当数据请求时间超过该设定时间时终止请求,这里我设置了5秒。之前我们在发送post的请求时发现,Vue默认的请求头是application/x-www-form-unlencoded,这种方式Django后台无法接受到数据,所以我这直接给axios重新定义请求头为Content-Type: application/x-www-form-urlencoded。
axios.defaults.timeout = 5000;
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset-UTF-8';
3、请求拦截
在发送业务请求前进行阻拦,阻拦的目的是在数据发送前对数据进行一定逻辑的处理,比如统一添加token,当然前提是我们在登录的时候将token通过localStorage或者cookie存在本地并更新至store中,然后每次发送请求的时候就去store中提取对应的token赋给header,后台通过token来判断用户是否登录。
// 请求拦截器(请求发出前处理一些需求)
axios.interceptors.request.use(
config => {
// 每次发送请求之前判断store中
const token = store.state.token;
token && (config.headers.Authorization = token);
return config;
},
error => {
return Promise.error(error);
}
)
4、响应拦截
当axios拿到服务器返回给我们的数据,对数据进行一些统一的处理,这里主要针对异常处理,如果后台返回的状态码是200,则正常返回数据,如果请求失败,那么我们就可以根据错误的状态码类型进行一些常规的异常处理方案。
// 响应拦截器(处理响应数据)
axios.interceptors.response.use(
response => {
if(response.state === 200) {
return Promise.resolve(response);
} else {
return Promise.reject(response);
}
},
error => {
let status = error.response.status;
if (status) {
switch(status) {
case 401:
// 跳转登录页面并将要浏览的页面fullPath传过去
VueRouter.replace({
path: '/login',
query: { redirect: router.currentRoute.fullPath }
});
break;
case 500:
// ......具体状态码可以跟后台对接
break;
default:
Message.console.error(error.response.data.message);
break;
}
return Promise.reject(error.response);
}
}
)
5、封装get/post/put方法
定义一个get函数,传递有两个参数,参数1url地址,参数2携带的请求参数。然后返回一个promise对象,请求成功时resolve服务器返回 值,请求失败时reject错误。
post/put需要注意的就是需要对参数进行序列化操作,否则后台是拿不到你提交的数据,axios中提供了qs模块,可以对参数进行序列化,在axios中引入this.$qs.stringify(),这样在传参前将数据自动处理成键值对形式,这里我们可以看到发送的数据格式为Form Data(请求头 Content-Type: application/x-www-form-urlencoded),后台也正常接收到了数据。
补充:
在POST请求中的
Content-Type
常见的有以下3种形式:
- Content-Type: application/json
- Content-Type: application/x-www-form-urlencoded
- Content-Type: multipart/form-data
现在主流基本在用application/json形式,Axios默认以这种形式工作,我们给后端接口传递参数也简单,直接放在data参数就行了。
// 封装get请求(query请求)
function get(url, params) {
return new Promise((resolve, reject) => {
axios.get(url, {
params
}).then(res => {
resolve(res.data);
}).catch(err => {
reject(err.data)
})
})
}
// 封装post请求
function post(url, params) {
return new Promise((resolve, reject) => {
axios.post(url, qs.stringify(params))
.then(res =>{
resolve(res.data);
}).catch(err =>{
reject(err.data);
})
})
}
// 封装put请求
function put(url, params) {
return new Promise((resolve, reject) => {
axios.put(url,qs.stringify(params))
.then(response => {
resolve(response.data);
}).catch(err => {
reject(err.data);
})
})
}
二次封装:
/*
*url:请求的url
*params:请求的参数
*config:请求时的header信息
*method:请求方法
*/
const request = function ({ url, params, config, method }) {
// 如果是get请求 需要拼接参数
let str = ''
if (method === 'get' && params) {
Object.keys(params).forEach(item => {
str += `${item}=${params[item]}&`
})
}
return new Promise((resolve, reject) => {
axios[method](str ? (url + '?' + str.substring(0, str.length - 1)) : url, params, Object.assign({}, config)).then(response => {
resolve(response.data)
}, err => {
if (err.Cancel) {
} else {
reject(err)
}
}).catch(err => {
reject(err)
})
})
}
这样我们需要接口请求的时候,直接调用该函数就好了。不管什么方式请求,传参方式都一样。
6、抛出方法
写个导出函数,将方法抛出去,这样其他组件就可以访问到我们封装的方法了,通过提交的方法来判断我们该调用哪个方法。
// 抛出函数,对外的接口
export default function request (method, url, params) {
if( method === 'get') {
return get(url, params);
} else if( method === 'post') {
return post(url, params);
} else if( method === 'put') {
return put(url, params);
}
}
7、全局注册
全局引入axios.js文件,这样就不用在组件中去调用了,因为我在axios中只有一个默认的函数抛出,所以我们不需要指定方法,直接调用该文件即可。
import request from '@/utils/axios.js'
import Vue from 'vue';
Vue.prototype.$axios = request;
8、组件中使用
this.$axios('post', 'http://127.0.0.1:8000/login', {
username: this.username,
password: this.password
})
.then(res =>{
if(res.data['code'] == 200) {
this.$store.commit('setToken',res.data.token);
this.$message({
message: '登录成功',
type: 'success'
})
this.$router.push('/home/info')
}
})
.catch(err => console.log(err))
8、组件中使用
this.$axios('post', 'http://127.0.0.1:8000/login', {
username: this.username,
password: this.password
})
.then(res =>{
if(res.data['code'] == 200) {
this.$store.commit('setToken',res.data.token);
this.$message({
message: '登录成功',
type: 'success'
})
this.$router.push('/home/info')
}
})
.catch(err => console.log(err))