出现场景
- 同一个tab来回切换
- 根据输入单词查询接口
- 两个按钮异步的形式操作同一份数据
解决
ui层面
防抖 全局loading
总结: 阻塞用户操作
防抖
function debounce(fun, delay) {
return function (args) {
let that = this
let _args = args
clearTimeout(fun.id)
fun.id = setTimeout(function () {
fun.call(that, _args)
}, delay)
}
}
对于输入框实时请求
闭包+对请求加id校验 +单实例
// 请求标记
let gobalReqID = 0
// 请求的函数
funtion query (keyword) {
gobalReqID++
let curReqID = gobalReqID
return axios.post('/list', {
keyword
}).then(res => {
// 对比闭包内的 curReqID 是否和 gobalReqID 一致
if (gobalReqID === curReqID) {
return res
} else {
return Promse.reject('无用的请求')
}
})
}
总结: 适合简单应用,可并发 ,无需要重复请求的诉求,多个组件同时初始化发请求搞不定
多实例 请求带着id,回来带着id,, tab多的话有性能问题
candelToken
其他补充:缓存请求也可以这里添加,原理同白名单类似
var CancelToken = axios.CancelToken;
var source = CancelToken.source();
axios.get('/user/12345', {
cancelToken: source.token
}).catch(function(thrown) {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else {
// 处理错误
}
});
// 取消请求(message 参数是可选的)
source.cancel('Operation canceled by the user.');
//App.vue
export default {
data() {
return {
//当前正在执行的请求列表
requestList: [],
//可重复请求的白名单列表
sameRequestUrlWhiteList: []
};
},
created() {
//...其他操作
this.setAxiosInterceptors();
},
methods: {
setAxiosInterceptors() {
this.$http.interceptors.request.use(request => {
//给请求用单例模式配置一个独有的cancelToken
request.cancelToken = new axios.CancelToken(function executor(c) {
// 把cancel方法赋值到request的cancel属性上
request.cancel = c;
});
//判断当前请求是否在可重复请求的白名单中,如果不在,则进入
if (!this.sameRequestUrlWhiteList.includes(request.url)) {
//对requestList做过滤,从requestList中移除重复请求的同时,执行cancel方法
this.requestList = this.requestList.filter(item => {
if (
//通过url和method双重校验
item.url === request.url &&
item.method === request.method
) {
//取消请求,同时返回false给filter
item.cancel();
return false;
}
//不做操作,返回false给filter
return true;
});
}
//将当前请求push到requestList中
this.requestList.push(request);
//...其他处理
return request;
});
this.$http.interceptors.response.use(
response => {
//当接口响应之后,从 requestList 中移除
this.requestList = this.requestList.filter(item => {
if (
item.url === response.config.url &&
item.method === response.config.method
) {
return false;
}
return true;
});
//...其他处理
return response;
},
error => {
//错误处理
}
);
}
}
}
cancel后 会报错 错误处理
error => {
if (axios.isCancel(error)) {
//处理被取消的错误
} else {
//处理正常错误
this.$Message.error({
content: "请求失败,请联系管理员"
});
}
return Promise.reject(error.response);
}
总结: 可并发
异步队列
/**
* Using:
* const queue = new RafAnimationQueue(defaultContext)
* queue.add(firstAnimationFrameCallback) // firstAnimationFrameCallback will executed with defaultContext
* queue.add(secondAnimationFrameCallback, customContext)
* queue.delay() // skip just one frame
* queue.clear() // clear animation queue
**/
export default class RafAnimationQueue {
constructor(context) {
this.context = context || undefined;
this.clear();
}
add(callback, context) {
if (callback instanceof Function) {
this.queue.push(callback.bind(context || this.context));
this.runQueue();
}
return this;
}
delay() {
this.queue.push(undefined);
this.runQueue();
return this;
}
runQueue() {
if (this.queueInProgress) {
return;
}
this.queueInProgress = true;
requestAnimationFrame(this.queueLoop.bind(this));
}
queueLoop() {
const callback = this.queue.shift();
callback instanceof Function && callback();
if (!this.queue.length) {
this.queueInProgress = false;
} else {
requestAnimationFrame(this.queueLoop.bind(this));
}
}
// 加 await ,支持传入 异步函数 ,执行完后才执行下一个
// async queueLoop() {
// const callback = this.queue.shift();
// callback instanceof Function && await callback();
// if (!this.queue.length) {
// this.queueInProgress = false;
// } else {
// requestAnimationFrame(this.queueLoop.bind(this));
// }
// }
clear() {
this.queue = [];
}
}
rxjs switchmap
var btn = document.querySelector('.js-query');
var inputStream = Rx.Observable.fromEvent(btn, 'click')
.debounceTime(250) // 防抖,防止请求过于频繁
.switchMap(url => Http.get(url))
.subscribe(data => render(data));