某條前端面試題--實現一個封裝的ajax器

題目描述

實現一個封裝ajax器,功能有ios

  1. 限制一次同時發送的ajax請求數量m個
  2. timeout限制
  3. 重試n次

解題思路

  • 強調下,個人想法和代碼只是嘗試回答面試問題,並不能直接在正式的業務場景裏使用,但但願也能給大家帶去思考。另外,個人解決方案是基於promise完成的。
  • 首先,限制條件1在個人理解中,發送的請求還有m個請求在請求中,那麼這時程序發出的請求,要先被存下來,等到有請求結束了,再發送給服務器。這就須要咱們思考用什麼結構,存儲請求,考慮到請求是先進先出的,那麼就可使用隊列。
  • 其次,二、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就會解決或拒絕。ajax

咱們利用的就是race方法返回一個被包裝的Promise,咱們基於這個作回調處理,裏面有用來取消的Promise,和原始的Promise,當須要取消Promise,用來取消的Promise resolve就能夠了。axios

//返回能夠取消的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);
    }

}
複製代碼

測試代碼promise

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");
複製代碼

做者菜,若有不對,請快點指出,多多見諒!bash

相關文章
相關標籤/搜索