1. 安装axios
npm install axios -S
2. 新增axios.js文件, 基本架子如下:
class HttpAxios {
instance;
method = 'post';
timeout = 300 * 1000;
constructor(config) {
this.instance = axios.create(config);
}
sendRequest = (url, params, method = 'post', config) => {
if (!this.instance) {
return;
}
this.method = method;
const _method = method.toLocaleLowerCase();
if (_method === 'get') {
return this.instance.get(url, { params });
}
let repData;
if (_method === 'formdata') {
reqData = new FormData();
for (let key in params) {
reqData.append(key, params[key]);
}
}
return this.instance.post(url, repData || params);
};
}
3. 新增请求拦截器
class HttpAxios {
constructor(config) {
instance.interceptors.request.use(this._requestInterceptors, this._checkRequestError);
instance.interceptors.response.use(this._responseInterceptors, this._checkResponseError);
}
_requestInterceptors = (config) => {
let _config = {
withCredentials: false,
timeout: this.timeout
};
if (this.method == 'formate') {
config.headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
}
config.headers = Object.assign({}, config.headers, _header);
return Object.assign({}, config, _config);
}
_checkRequestError = (error) => {
return Promise.reject(error);
}
};
4. 新增响应拦截器
class HttpAxios {
constructor(config) {
instance.interceptors.response.use(this._responseInterceptors, (error) => {
return Promise.reject(error);
});
}
_responseInterceptors = (response) => {
let data = response.data || {};
if (data.code === 200) {
return data.data || '' ;
}
this._checkResponseCode(data);
return undefined;
}
_checkResponseError = (error) => {
if (/timeout/g.test(error)) {
return Promise.reject(error);
}
const { response } = error;
const { status, statusText, data } = response || {};
let msg = data?.message || CODE_MESSAGE[status] || statusText;
return Promise.reject(error);
};
}
5. 取消请求处理:给每个请求添加cancelToken,并在请求取消时取消请求,并新增配置,是否在切换页面时清除请求标记
1. 新增map对象,用于存储每个请求的标记
class HttpAxios {
cancelTokenArr = [];
clearFlag = true;
}
2. 在请求拦截中设置cancelToken
class HttpAxios {
_requestInterceptors = (config) => {
config.cancelToken = new axios.CancelToken((cancel) => {
this.cancelTokenArr.push({ cancel, url: config.url });
});
}
}
3. 添加清除函数
class HttpAxios {
async cancelRequest(url) {
if (this.cancelTokenArr.length === 0) {
return;
}
for (let i = 0; i < this.cancelTokenArr.length; i++) {
if (this.cancelTokenArr[i].url === url) {
this.cancelTokenArr[i].cancel();
this.cancelTokenArr.splice(i, 1);
break;
}
}
}
clearAllRequest = () => {
this.cancelTokenArr.forEach((item) => {
item.cancel();
});
this.cancelTokenArr = [];
};
}
4. 例如在home.vue中使用
<script setup>
import HelloWorld from '@/components/HelloWorld.vue';
import api, { httpAxios } from '@/api';
import { sleep } from '@/utils';
import demo from '@/api/demo';
const request = async () => {
const res = await api.demo({msg: '123'});
console.log(res);
}
request();
setTimeout(() => {
console.log('setTimeout');
httpAxios.cancelRequest(demo.demo.url);
}, 1000);
</script>
6. 添加失败重试机制
1. 修改返回拦截器,添加失败重试
class HttpAxios {
_responseInterceptors = async (response) => {
let data = response.data || {};
if (data.code === 200) {
return data.data || '' ;
}
await this._retryRequest(response.config)
return undefined;
};
_checkResponseError = async (error) => {
await _retryRequest(error.config);
if (/timeout/g.test(error)) {
return Promise.reject(error);
}
const { response } = error;
const { status, statusText, data } = response || {};
let msg = data?.message || CODE_MESSAGE[status] || statusText;
return Promise.reject(error);
};
_retryRequest = async (config) => {
if (config && config.maxRetries && config.retryCount < config.maxRetries) {
config.retryCount = (config.retryCount || 0) + 1;
await sleep(config.retryDelay || 1000);
return this.instance(config);
}
};
2. 在请求中添加配置
sendRequest = (url, params, method = 'post', config = {}) => {
if (!this.instance) {
return;
}
this.method = method;
if (config?.maxRetries) {
config.retryCount = 0;
}
const _method = method.toLocaleLowerCase();
if (_method === 'get') {
params = {
params: params
};
return this.instance.get(url, {...params, ...config});
}
if (_method === 'formdata') {
let reqData = new FormData();
for (let key in params) {
reqData.append(key, params[key]);
}
return this.instance.post(url, reqData, config);
}
return this.instance.post(url, params, config);
};
7. 其他:比如结合elementUI实现请求loading效果等
class HttpAxios {
loadingInstance = null;
_showLoading(config) {
if (config.showLoading && !this.loadingInstance) {
this.loadingInstance = ElLoading.service({
lock: true,
text: config.loadingText || '加载中...',
background: 'rgba(0, 0, 0, 0.7)'
});
}
this.requestCount++;
}
_hideLoading() {
this.requestCount--;
if (this.requestCount === 0 && this.loadingInstance) {
this.loadingInstance.close();
this.loadingInstance = null;
}
}
_requestInterceptors = (config) => {
this._showLoading(config);
let _config = {
withCredentials: false,
timeout: this.timeout
};
let _header = {
};
if (this.method == 'formate') {
_header['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
}
config.headers = Object.assign({}, config.headers, _header);
config.cancelToken = new axios.CancelToken((cancel) => {
this.cancelTokenArr.push({ cancel, url: config.url });
});
return Object.assign({}, config, _config);
};
_responseInterceptors = (response) => {
this._hideLoading();
}
_checkResponseError = async (error) => {
await this._retryRequest(error.config);
this._hideLoading();
if (/timeout/g.test(error)) {
return Promise.reject(error);
}
const { response } = error;
const { status, statusText, data } = response || {};
let msg = data?.message || CODE_MESSAGE[status] || statusText;
return Promise.reject(error);
};
}
8. 新增index.js导出实例
import { getHttpAxiosInstance } from "@/api/axios";
import demo from "@/api/demo";
export const httpAxios = getHttpAxiosInstance()();
function toMethod(options) {
options.method = options.method || 'post';
const { method = 'post', url, config } = options;
return (param = {}, _config = {}) => {
return httpAxios.sendRequest(url, param, method, {...config, _config});
}
}
export function generateApiMap(maps) {
let methodMap = {};
for(let key in maps) {
methodMap[key] = toMethod(maps[key]);
}
return methodMap;
}
export default {
...generateApiMap({
...demo
})
}
9. 完整的axios.js代码
import axios from 'axios';
const CODE_MESSAGE = {
200: '服务器成功返回请求的数据。',
201: '新建或修改数据成功。',
202: '一个请求已经进入后台排队(异步任务)。',
204: '删除数据成功。',
400: '信息校验失败',
401: '用户没有权限(令牌、用户名、密码错误)。',
403: '用户得到授权,但是访问被禁止',
404: '请求的资源不存在',
406: '请求的格式不可得。',
410: '请求的资源被永久删除,且不会再得到的。',
422: '当创建一个对象时,发生一个验证错误。',
500: '服务器发生错误,请检查服务器。',
502: '网关错误。',
503: '服务不可用,服务器暂时过载或维护。',
504: '网关超时。'
};
class HttpAxios {
instance;
method = 'post';
timeout = 300 * 1000;
loadingInstance;
requestCount = 0;
cancelTokenArr = [];
clearFlag = true;
constructor(config) {
let instance = axios.create(config);
instance.interceptors.request.use(this._requestInterceptors, (error) => {
return Promise.reject(error);
});
instance.interceptors.response.use(this._responseInterceptors, this._checkResponseError);
this.instance = instance;
}
_showLoading(config) {
if (config.showLoading && !this.loadingInstance) {
this.loadingInstance = ElLoading.service({
lock: true,
text: config.loadingText || '加载中...',
background: 'rgba(0, 0, 0, 0.7)'
});
}
this.requestCount++;
}
_hideLoading() {
this.requestCount--;
if (this.requestCount === 0 && this.loadingInstance) {
this.loadingInstance.close();
this.loadingInstance = null;
}
}
_requestInterceptors = (config) => {
this._showLoading(config);
let _config = {
withCredentials: false,
timeout: this.timeout
};
let _header = {
};
if (this.method == 'formate') {
_header['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
}
config.headers = Object.assign({}, config.headers, _header);
config.cancelToken = new axios.CancelToken((cancel) => {
this.cancelTokenArr.push({ cancel, url: config.url });
});
return Object.assign({}, config, _config);
};
_responseInterceptors = async (response) => {
let data = response.data || {};
if (data.code === 200) {
return data.data || '' ;
}
await this._retryRequest(response.config);
this._hideLoading();
const { code, message } = data;
return undefined;
};
_retryRequest = async (config) => {
if (config && config.maxRetries && config.retryCount < config.maxRetries) {
config.retryCount = (config.retryCount || 0) + 1;
await sleep(config.retryDelay || 1000);
return this.instance(config);
}
};
_checkResponseError = async (error) => {
await this._retryRequest(error.config);
this._hideLoading();
if (/timeout/g.test(error)) {
return Promise.reject(error);
}
const { response } = error;
const { status, statusText, data } = response || {};
let msg = data?.message || CODE_MESSAGE[status] || statusText;
return Promise.reject(error);
};
_checkResponseCode = ({ code, message }) => {
if (code === '401') {
sessionStorage.setItem('route_to_login', location.href);
}
};
sendRequest = (url, params, method = 'post', config = {}) => {
if (!this.instance) {
return;
}
this.method = method;
if (config?.maxRetries) {
config.retryCount = 0;
}
const _method = method.toLocaleLowerCase();
if (_method === 'get') {
params = {
params: params
};
return this.instance.get(url, {...params, ...config});
}
if (_method === 'formdata') {
let reqData = new FormData();
for (let key in params) {
reqData.append(key, params[key]);
}
return this.instance.post(url, reqData, config);
}
return this.instance.post(url, params, config);
};
async cancelRequest(url) {
if (this.cancelTokenArr.length === 0) {
return;
}
for (let i = 0; i < this.cancelTokenArr.length; i++) {
if (this.cancelTokenArr[i].url === url) {
this.cancelTokenArr[i].cancel();
this.cancelTokenArr.splice(i, 1);
break;
}
}
}
async clearRequests() {
if (this.cancelTokenArr.length === 0 || !this.clearFlag) {
return;
}
this.cancelTokenArr.forEach((ele) => {
ele.cancel();
});
this.cancelTokenArr = [];
}
}
export const getHttpAxiosInstance = () => {
let instance = null;
return () => {
if (!instance) {
instance = new HttpAxios({});
}
return instance;
}
}