在平时的单页面项目里,大家肯定接触过axios库,一个易用、简洁且高效,使用Promise管理异步,告别传统callback方式的http库。

最近有个项目里接口调取的频率比较高,接口队列长,然后等待数据的时间就是痛苦的煎熬,特别是一连串有关联的数据交互,每次打开页面我都有种欲死的感觉。经过一番深思改造后,除去接口本身的速度,对于页面的流畅度提高了不少,所以今天就和大家分享一下,怎么封装二次Axios,取消重复请求和接口缓存。安装Axios

npm install axiosAxios基本配置

根目录下新建axios.config.js文件,先写入请求拦截器,响应拦截器,其中要注意接口请求失败的信息处理,根据status判断是否要进行额外的处理,例如下面例子判断statue为401则清除token,重新登录,如果有error.message 就返回错误信息代码100002,用于页面判断显示错误信息。

/** axios封装 请求拦截、响应拦截 */
import axios from 'axios';
import router from '../router';
import qs from 'qs';
/** 设置post请求头 */
instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
/** 请求拦截器*/
instance.interceptors.request.use(
(config) => {
let token = window.localStorage.getItem('token');
token = token ? token : '';
token && (config.headers.Authorization = token);
return config;
},
(error) => Promise.reject(error)
);
/** 响应拦截器 */
instance.interceptors.response.use(
(res) => {
return Promise.resolve(res);
},
// 请求失败(error) => {
if (error) {
let { response } = error;
/** 浏览器报错 */
if (response) {
/** 登录过期 */
if (response.status == 401) {
localStorage.removeItem('token');
router.replace('/login');
}
}
if (error.message) {
if (typeof error.message === 'string') {
response = { code: 100002, message: error.message };
}
} else {
response = {};
}
return Promise.resolve(response ? response : { code: 100002 });
} else {
return Promise.resolve({ code: 100002 });
}
}
);

export default instance;取消重复接口

新建一个数组taskList用于存储接口请求任务队列,接口请求流程如下图。例如请求A接口的时候,先判断A接口是否存在于taskList里,如若存在则axios.CancelToken方法取消前一个A接口,然后往队列里增加本次A接口,等到请求完成的时候,再将A接口移除taskList,以此类推就完成一个动态任务队列啦。

/** axios封装 请求拦截、响应拦截 */
import axios from 'axios';
import router from '../router';
const CancelToken = axios.CancelToken,
apiCach = {
taskList: [] /** 请求任务列表 */,
/** 新增任务 */
addTask(config, cancelToken) {
this.taskList.push({ original: `${config.url}&${config.method}`, cancelToken });
},
/** 删除任务 */
deleteTask(config, start, cancelToken) {
let cancel = false;
for (let i in this.taskList) {
if (this.taskList[i]['original'] == `${config.url}&${config.method}`) {
this.taskList[i].cancelToken('');
this.taskList.splice(i, 1);
cancel = true;
break;
}
}
},
},
/** 创建axios实例 */
instance = axios.create({
timeout: 1000 * 12,
});
/** 设置post请求头 */
instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
/** 请求拦截器*/
instance.interceptors.request.use(
(config) => {
config.cancelToken = new CancelToken((c) => {
/** 删除任务 */
apiCach.deleteTask(config, true, c);
/** 新增任务*/
apiCach.addTask(config, c);
});
config.headers.expirationTime = void 0;
let token = window.localStorage.getItem('token');
token = token ? token : '';
token && (config.headers.Authorization = token);
return config;
},
(error) => Promise.reject(error)
);
/** 响应拦截器 */
instance.interceptors.response.use(
(res) => {
apiCach.deleteTask(res.config, false);
return Promise.resolve(res);
},
// 请求失败(error) => {
if (error) {
let { response } = error;
/** 浏览器报错 */
if (response) {
if (response.status == 401 || response.status == 403) {
localStorage.removeItem('token');
router.replace('/login');
}
}
if (error.message) {
if (typeof error.message === 'string') {
response = { code: 100002, message: error.message };
}
} else {
response = {};
}
return Promise.resolve(response ? response : { code: 100002 });
} else {
return Promise.resolve({ code: 100002 });
}
}
);

export default instance;接口缓存,避免资源过度消耗

对于调用频率高、数据变化较小的接口,可以根据情况进行适当时间的缓存,第二次调取的时候直接取缓存加载,既可以优化接口任务队列,更能节省时间和资源。

新建一个Map集合cachMap用于存放接口缓存集合,接口进行流程有所改变,如下图。请求A接口的时候,先判断是否有同样的请求存在于队列中,如有则取消,不同点在于,如若接口没有取消则判断接口则判断是否有缓存且处于有效期内,判断成功则返回缓存,反则队列新增接口,请求完成的时候将结果缓存,请求A移除队列。

/** axios封装 请求拦截、响应拦截 */
import axios from 'axios';
import router from '../router';
import store from '../store/index';
import qs from 'qs';
const CancelToken = axios.CancelToken,
apiCach = {
cachMap: new Map() /** 缓存列表 */,
taskList: [] /** 请求任务列表 */,
/** 新增任务 */
addTask(config, cancelToken) {
this.taskList.push({ original: `${config.url}&${config.method}`, cancelToken });
},
/** 删除任务 */
deleteTask(config, start, cancelToken) {
let cancel = false;
for (let i in this.taskList) {
if (this.taskList[i]['original'] == `${config.url}&${config.method}`) {
this.taskList[i].cancelToken('');
this.taskList.splice(i, 1);
cancel = true;
break;
}
}
if (!cancel && start) {
this.deleteCach(config, cancelToken);
}
},
/** 创建key */
createKey(config) {
let str = '';
config.url && (str += config.url);
if (config.method) {
str += ',method:' + config.method;
if (config.method === 'get') {
str += ',data:' + qs.stringify(config.params) + '';
} else {
str += ',data:' + config.data;
}
}
return str;
},
/** 删除缓存 */
deleteCach(config, cancelToken) {
let cachMap = this.cachMap;
const key = this.createKey(config),
now = new Date().getTime();
let cach = cachMap.get(key) || {};
if (cach && cach.expirationTime && now <= cach.deadline && cach.data) {
cach.cach = true;
cancelToken(cach.data);
}
},
/** 新增缓存 */
addCach(config, cancel) {
const key = this.createKey(config),
expirationTime = config.headers.expirationTime || 0;
if (expirationTime) {
this.cachMap.set(key, { expirationTime, deadline: new Date().getTime() + expirationTime, data: '', cancel });
}
},
/** 更新缓存 */
updateCach(res) {
if (!res || !res.config) {
return false;
}
const key = this.createKey(res.config),
oldVal = this.cachMap.get(key);
if (!oldVal) {
return false;
}
this.cachMap.set(key, { expirationTime: oldVal.expirationTime, deadline: oldVal.deadline, data: res });
},
},
/** 创建axios实例 */
instance = axios.create({
timeout: 1000 * 12,
});
/** 设置post请求头 */
instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
/** 请求拦截器*/
instance.interceptors.request.use(
(config) => {
config.cancelToken = new CancelToken((c) => {
/** 删除任务 | 缓存 */
apiCach.deleteTask(config, true, c);
/** 新增任务 | 缓存 */
apiCach.addTask(config, c);
apiCach.addCach(config, c);
});
config.headers.expirationTime = void 0;
let token = window.localStorage.getItem('token');
token = token ? token : '';
token && (config.headers.Authorization = token);
return config;
},
(error) => Promise.reject(error)
);
/** 响应拦截器 */
instance.interceptors.response.use(
(res) => {
apiCach.deleteTask(res.config, false);
apiCach.updateCach(res);
return Promise.resolve(res);
},
// 请求失败(error) => {
if (error) {
let { response } = error;
/** 浏览器报错 */
if (response) {
if (response.status == 401 || response.status == 403) {
localStorage.removeItem('token');
store.commit('loginSuccess', null);
router.replace('/login');
}
}
if (error.message) {
if (typeof error.message === 'string') {
response = { code: 100002, message: error.message };
} else if (typeof error.message === 'object') {
response = error.message;
}
} else {
response = {};
}
return Promise.resolve(response ? response : { code: 100002 });
} else {
return Promise.resolve({ code: 100002 });
}
}
);

export default instance;

接口请求的时候,如果需要缓存需要在header里增加expirationTime(ms),代码如下。

return axios.post('/example', qs.stringify(params), {
headers: {
expirationTime : 60*1000,//缓存一分钟内有效 }
});

接口请求的时候回重定义exprianTime字段为空。

最后我还考虑过,如接口缓存时间较长,可以考虑将apiCach里的cachMap缓存于localstorage,然后定期更新,这样每次打开页面或者刷新的时候,也可以有效缓解首页加载速度,但是目前项目利用率不是很高,所以就没使用。