实现一个封装ajax器,功能有
限制一次同时发送的ajax请求数量m个
timeout限制
重试n次
解题思路
强调下,我的想法和代码只是尝试回答面试问题,并不能直接在正式的业务场景里使用,但希望也能给你们带去思考。另外,我的解决方案是基于promise完成的。
首先,限制条件1在我的理解中,发送的请求还有m个请求在请求中,那么这时程序发出的请求,要先被存下来,等到有请求结束了,再发送给服务器。这就需要我们思考用什么结构,存储请求,考虑到请求是先进先出的,那么就可以使用队列。
其次,2、3这两个限制条件,合起来说就超时重试了,重试了n次后,就当这个请求失败了,不再尝试。这个超时重试如何实现呢?我的理解是,发送一次后,加个定时器,当超时后,就会执行定时器里的代码,取消这次请求,尝试下次请求,为这个请求的重试次数计数。这就需要实现一个可以取消的Promise。
队列
实现一个队列,一般要实现进队、出队的方法,符合先进先出的原则,同时也要实现查询队满、队空的状态。 因为如果进出队次数过多会造成数组过大,为了不造成浪费,这里就实现的是循环队列。
//实现一个队列
class Queue{
constructor(size){
this.size = size;
this.data = Array(size);
this.front = 0;
this.rear = 0;
}
in(url){
if(this.isFull()) return false;
this.data[this.rear] = url;
this.rear = (this.rear+1)%this.size;
return true;
}
out(){
let res = null;
if(this.isEmpty()) return res;
res = this.data[this.front];
this.data[this.front] = null;
this.front = (this.front+1)%this.size;
return res;
}
isFull(){
return this.front===this.rear&&this.data[this.size-1];
}
isEmpty(){
return this.front===this.rear&&!this.data[this.size-1];
}
}
复制代码
可取消的Promise
Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。
我们利用的就是race方法返回一个被包装的Promise,我们基于这个做回调处理,里面有用来取消的Promise,和原始的Promise,当需要取消Promise,用来取消的Promise resolve就可以了。
//返回可以取消的Promise
class CanCancelPromise{
constructor(promise){
let resolve,reject,cancelP;
if(promise instanceof Promise){
cancelP = new Promise(function(res, rej){
resolve = res;
reject = rej;
});
this.promise = Promise.race([promise, cancelP]);
this.cancel = ()=>{
resolve(CancelPromise);
};
}else{
throw new Error("请传入Promise对象")
}
}
}
复制代码
实现ajax封装器
当前面两部准备工作完成,就可以借助它们来实现ajax封装器了。
class axios{
/**
* 初始化axios
* @param maxSize 限制同时请求的ajax请求数量m
* @param reTryCount 重试次数
* @param timeout 超时设定时间
* @param poolSize 请求池的大小
*/
constructor(maxSize=3,reTryCount=3,timeout = 100,poolSize=100){
this.requests = new Queue(poolSize);
this.maxSize = maxSize;
this.curSize = 0;
this.timeout = timeout;
this.reTryCount = reTryCount;
}
//传入请求url和回调函数
post(url,handle=null){
if(this.requests.in(url)){
return this.tryRequestUrl(handle);
}else{
throw new Error("请求池已满");
}
}
//存入请求池,开始根据最大请求判断是否发出请求
tryRequestUrl(handle){
if(this.curSize<this.maxSize){
let url = this.requests.out();
if(url){
console.log(`开始连接${url}`);
this.reTryRequest(url,handle,this.timeout,this.reTryCount);
this.curSize ++;
}
}else{
console.log(`等待连接`);
}
}
// 请求并负责重试
reTryRequest(url,handle, wait,num) {
let timeout, i=1;
let request = (url)=>{
//模拟url请求,现实场景中可以fetch代替
//这里的请求很容易超时
let promiseObj = new CanCancelPromise(new Promise((resolve, reject) => {
setTimeout(resolve, Math.random()*1000);
}));
promiseObj.promise.then((value)=>{
if(value===CancelPromise){
console.log("取消成功");
return;
}
if(timeout){
clearInterval(timeout);
timeout = null;
}
handle(value);
console.log(`请求${url}第${i-1}次成功`);
this.curSize--;
this.tryRequestUrl();
i = num;
});
return promiseObj;
};
let pObj = request(url);
//负责重试
timeout = setInterval(() => {
pObj.cancel();
if(i===num){
clearInterval(timeout);
timeout = null;
console.log(`请求${url}重试${i}次失败,不再重试!`);
}else{
pObj = request(url);
console.log(`请求${url}重试${i}次失败`);
this.curSize--;
this.tryRequestUrl();
}
i++;
}, wait);
}
}
复制代码
测试代码
let a = new axios();
a.post("baidu1.com");
a.post("baidu2.com");
a.post("baidu3.com");
a.post("baidu4.com");
a.post("baidu5.com");
a.post("baidu6.com");
复制代码
有想了解更多的小伙伴可以加Q群链接里面看一下,应该对你们能够有所帮助。
网友评论