题目描述
实现一个封装ajax器,功能有
- 限制一次同时发送的ajax请求数量m个
- timeout限制
- 重试n次
解决方案
之前的解决方案我放在自己的博客上了,主要是实现队列、可取消的Promise,借此来构建,但ajax器部分的代码耦合性太强了,就不留在这里。
最近看到了如何实现 Promise 的限流: Promise.map 的简单实现,感觉自己写的代码冗长了,像老太婆的裹脚布一般,写一段能够实现功能的松散的代码很简单,但是写简洁易复用易扩展的代码就很困难了。所以在它的基础上重写了一版。我的想法和代码只是尝试回答面试问题,并不是完美的解决方案,但希望也能给你们带去思考,觉得有用可点个赞。
实现思路
- 去除了队列这个类,没必要,用数组就可以简单实现,但是这样可能会造成性能问题,因为数组会在无限扩充,当增长当一定级别,性能堪忧。
- 最重要的实现,在外层多套了一个Promise(暂时称为主Promise),外部的异步处理也是在基于主Promise,将主Promise的resolve、reject函数加入队列,但真实的请求是在内部套的Promise(暂时称为子Promise)中,子Promise根据自身的状态来改变主Promise的状态。
- 限流的实现,也就可以暂时挂起主Promise,不进行真实的请求。等到有一个Promise完成,再去检查队列,执行子Promise。
- 超时的实现,和之前一样,在子promise放一个定时器,到时候就rejected
- 重试的实现,维护子Promise,子Promise状态变为rejected,检查下有没有重试机会,有的话,再将给主Promise创建一个子Promise,加入请求队列。这里是实现方式有点不简洁,一直在传参数,待改进。
- 不过也有点进步,就是将超时和重试这两个功能,写成了可配置的,就是请求时,如果需要错误重试,再传入。如果以后要提供其他功能,再加函数即可,可扩展性提升。
看一下使用方式:
const mapUrl = function(){
const a = new Axios(3);
for (let url of arguments) {
a.post(url,Axios.timeout(300),Axios.retry(3)).then(value=>{
console.log(value);
}).catch((e)=>{
console.log(url+e.message);
});
}
}
mapUrl("baidu1.com","baidu2.com","baidu3.com","baidu4.com","baidu5.com","baidu6.com");
实现代码
这一版的代码少了些
class Axios {
constructor (n) {
this.limit = n
this.count = 0
this.queue = []
}
enqueue (fn,promise=null,resolve=null,reject=null) {
if(promise){
this.queue.push({fn, resolve, reject});
this.queue.push(promise);
return promise;
}
//主Promise
let p = new Promise((resolve, reject) => {
this.queue.push({fn, resolve, reject })
})
this.queue.push(p);
return p;
}
dequeue () {
// 等到 Promise 计数器小于阈值时,则出队执行
if (this.count < this.limit && this.queue.length) {
const { fn, resolve, reject} = this.queue.shift();
const p = this.queue.shift();
this.run(p, fn,resolve,reject);
}
}
// async/await 简化错误处理
async run (p, fn,resolve,reject) {
try{
this.count++
const value = await fn(p,resolve,reject);
this.count--;
this.dequeue();
//释放主Promise,改变状态为resolved
resolve(value);
}catch(e){
this.count--;
this.dequeue();
}
}
build (fn,promise) {
let p = this.enqueue(fn,...promise);
this.dequeue();
return p;
}
post(url){
let fns = [],len = arguments.length-1,parentPromise=[],hasRetry=false;
if(arguments.length>1){
url = arguments[0];
if(len>2&&arguments[len-2] instanceof Promise){
parentPromise = [].slice.call(arguments,len-2);
fns = [].slice.call(arguments,0,len-2);
}else{
fns = [].slice.call(arguments,0);
}
}
//子Promise,真实请求的地方
let request = (...parentP)=>{
let res,rej,promise;
//模拟post请求,本地测试方便
promise = new Promise((resolve, reject) => {
res = resolve;
rej = reject;
setTimeout(()=>{
resolve(url+"执行成功");
}, Math.random()*500);
setTimeout(()=>{
reject(url+"执行失败");
}, Math.random()*400);
})//模拟结束
//给它加入处理函数(错误重试等)
for (const fn of fns) {
if(typeof(fn) === "function"){
fn.bind(this)(promise,res,rej,fns,parentP);
if(fn.name === Axios.retry.name){
hasRetry = true;
}
}
}
//默认加上retry函数,状态改变rejected只在这个函数中处理
if(!hasRetry){
Axios.retry(0).bind(this)(promise,res,rej,fns,parentP);
}
return promise;
};
return this.build(request,parentPromise);
}
//错误重试
static retry(times){
return function retry(promise,resolve,reject,args,parentP){
promise.catch((v)=>{
if(this instanceof Axios&×>0){
for (let a of args) {
if(typeof(a) === "function" && a.name === Axios.retry.name){
times--;
a = Axios.retry(times);
}
}
//失败了,再给它加进请求队列
console.log(args[0]+"重试");
this.post(...args,...parentP);
}else{
//释放主Promise,改变状态为rejected
parentP[parentP.length-1](new Error("错误重试失败",v));
}
});
}
}
//timeout限制
static timeout(time){
return function timeout(promise,resolve,reject,args){
let t = setTimeout(()=>{
console.log(args[0]+"超时");
reject(args[0]+"超时");
},time);
promise.then(()=>{
clearTimeout(t);
t = null;
}).catch(()=>{
clearTimeout(t);
t = null;
});
}
}
}
执行结果如下:
作者菜,如有不对,请快点指出,多多见谅!