在开发PC端项目时,经常会出现上一个请求还在pending状态,用户又进行相同点击,出现多次发送相同请求的情况。为了减少请求次数,减少服务器压力,前端工程师们做了比较多的尝试。
- 一种方案是在操作按钮或者页面上加上loading蒙层,阻止用户点击。这种方式早期用的比较多,操作简单,不展开描述。
- 另一种方式就是今天在此记录的,axios使用之cancelToken,使用cancelToken取消请求的场景还分为如下两种:
- 对于上一个请求还在pending状态,相同的请求进行拦截
- 对于输入框实时请求,直接取消在pending中的请求,重新发送最新的请求
// utils/request.ts
// 接口默认pending状态下重复的请求不会发送
// 如果需要输入实时请求的接口,可以在请求参数中配置 remote:true
import axios from 'axios';
import { Message, Loading } from 'element-ui'
import baseURL from '../config';
let loading:any
let pending:Array<any> = []
let cancelToken = axios.CancelToken
let cancel:any
const service: any = axios.create({
baseURL,
timeout: 60000,
});
// serverless接口校验单点登录需要带如下参数
service.defaults.headers.common['x-client-ajax'] = '1';
service.defaults.withCredentials = true;
// 请求拦截器
service.interceptors.request.use(
(config: any) => {
let {method,url,baseURL} = config
let id = JSON.stringify({method,url:baseURL+url})
let index = pending.findIndex(p=>p.id===id)
if(config.remote){// 如远程下拉组件可带remote,true会重新发送,旧请求会被取消
removePending(id)
}
config.cancelToken = new cancelToken((c)=>{
pending.push({ id, cancel: c })
cancel = c
})
if(index>-1 && !config.remote){ // 如果不是远程请求,则会控制重复的请求不发送
cancel()
pending.splice(index, 1);
}
loading = loadingOpen(config.loading)
return config;
},
(error: any) => {
// Do something with request error
console.log(error); // for debug
Promise.reject(error);
},
);
// 响应拦截器
service.interceptors.response.use((res: any) => {
let {method,url} = res.config;
let id = JSON.stringify({method,url});
removePending(id); // 删除请求队列
loadingClose(); // 关闭全局loading
if (res.status === 418) return login();// 单点登录鉴权,判断status 418
const { responseType } = res.config;
const { resultCode,errorMsg,data } = res.data;
if (responseType === 'blob') return res;// 如果responseType为blob(导出接口),没有返回code值
if(resultCode === 0){ // 寻常接口判断resultCode
return Promise.resolve(data)
}else{
Message.error('接口繁忙:'+(errorMsg||'查询失败'))
return Promise.reject(errorMsg)
}
},
(error: any) => {
if (error && error.response && error.response.status === 418) {
return login();// 单点登录鉴权
}
if (axios.isCancel(error)){
return new Promise(()=>{}) // cancelToken
}
Message.error(error.message)
return Promise.reject(error); // 接口错误
}
);
// 登陆校验
const login = () => {
const origin = location.origin; // 这里是你想要用户登录成功后浏览器跳转回来的页面
location.href = `http://example.com/login?redirect=${encodeURIComponent(origin)}`;
};
let removePending = (_id:string) => {
let index = pending.findIndex(p=>p.id===_id)
if(index > -1){
pending[index].cancel()
pending.splice(index, 1); //把这条记录从数组中移除
}
}
let loadingOpen = (loading:boolean)=>{
let instance:any
if(loading){
instance = Loading.service({
lock:true,
text:'loading',
background:'transparent'
})
}
return instance
}
let loadingClose = ()=>{
loading && loading.close()
}
export default service;
- 下面是使用案例
// api/user.ts
import request from '@/utils/request';
export default {
// 在输入实时获取情况下remote设置为true
userInfo: (data:any,remote:Boolean): Promise<typing.AjaxPromise> => {
return request({
url: '/user/userInfo',
method: 'POST',
data,
remote
})
}
};
每天进步一点点