解析Axios原理之二:如何實現請求與響應的攔截

Axios攔截器在項目中所扮演的角色是非常重要的,它可以攔截每一次的請求和響應,然後進行相應的處理。經閱讀其源碼,不禁被作者的神級思維所折服!簡直是將Promise用到了極致!

1、聲明一個用於攔截器管理的構造函數
// 聲明攔截器管理構造函數
function InterceptorManager(){
    // 用於存放Axios攔截行爲及數據請求的Promise鏈條
    this.handlers = [];
}
// 增加攔截器
InterceptorManager.prototype.use = function (fulfilled,rejected) {
    this.handlers.push({
        fulfilled,
        rejected
    })
}
2、Axios 構造函數中增加 interceptors
function Axios(instanceConfig){
    // defaults 屬性爲配置對象
    this.defaults = instanceConfig;
    // interceptors 屬性用來設置請求和響應攔截器
    this.interceptors = {
        request: new InterceptorManager(),
        response: new InterceptorManager(),
    }
}
3、對之前封裝好的 request 進行調整
Axios.prototype.request = function (config) {
    function dispatchRequest(){
        // 發送xhr請求……
    }
    // dispatchRequest 發送請求,undefined 用來補位
    var chain = [dispatchRequest, undefined];
    // 遍歷實例對象的請求攔截器
    this.interceptors.request.handlers.forEach(interceptor=>{
        // 將請求攔截器壓入數組的最前面
        chain.unshift(interceptor.fulfilled,interceptor.rejected)
    })
    // 遍歷實例對象的響應攔截器
    this.interceptors.response.handlers.forEach(interceptor=>{
        // 將請求攔截器壓入數組的最後面
        chain.push(interceptor.fulfilled,interceptor.rejected);
    })
    // 創建一個成功的 promise 且成功的值爲合併後的請求配置
    let promise = Promise.resolve(config);

    // 如果鏈條長度不爲 0
    while (chain.length){
        // 依次取出 chain 的回調函數, 並執行
        promise = promise.then(chain.shift(),chain.shift())
    }
    return promise;
}
4、完整代碼
// 聲明攔截器管理構造函數
function InterceptorManager(){
    // 用於存放Axios攔截行爲及數據請求的Promise鏈條
    this.handlers = [];
}
// 增加攔截器
InterceptorManager.prototype.use = function (fulfilled,rejected) {
    this.handlers.push({
        fulfilled,
        rejected
    })
}

function Axios(instanceConfig){
    // defaults 屬性爲配置對象
    this.defaults = instanceConfig;
    // interceptors 屬性用來設置請求和響應攔截器
    this.interceptors = {
        request: new InterceptorManager(),
        response: new InterceptorManager(),
    }
}
Axios.prototype.request = function (config) {
    function dispatchRequest(){
        // 將請求方式全部轉爲大寫
        const method = (config.method || "get").toUpperCase();
        // 返回Promise
        return new Promise((resolve,reject)=>{
            // 聲明xhr
            const xhr = new XMLHttpRequest();
            // 定義一個onreadystatechange監聽事件
            xhr.onreadystatechange = function () {
                // 數據全部加載完成
                if(xhr.readyState === 4){
                    // 判斷狀態碼是否正確
                    if(xhr.status >= 200 && xhr.status < 300){
                        // 得到響應體的內容
                        const data = JSON.parse(xhr.responseText);
                        // 得到響應頭
                        const headers = xhr.getAllResponseHeaders();
                        // request 即是 xhr
                        const request = xhr;
                        // 狀態碼
                        const status = xhr.status;
                        // 狀態碼的說明
                        const statusText = xhr.statusText
                        resolve({
                            config,
                            data,
                            headers,
                            request,
                            status,
                            statusText
                        });
                    }else{
                        reject("請求失敗"+xhr.status+xhr.statusText);
                    }
                }
            }
            // http://127.0.0.1/two?a=1&b=2
            // 判斷是否擁有params,且類型爲object
            if(typeof config.params === "object"){
                // 將object 轉爲 urlencoded {a:1,b:2} a=1&b=2
                // ["a","b"]
                const arr = Object.keys(config.params);
                // ["a=1","b=2"]
                const arr2 = arr.map(v=>v+"="+config.params[v]);
                // a=1&b=2
                const url = arr2.join("&");
                // config.url = config.url + "?" + url;
                config.url +=  "?" + url;
            }
            xhr.open(method,config.url);
            // post put patch
            if(method === "POST" || method === "PUT" || method === "PATCH"){
                if(typeof config.data === "object")
                    xhr.setRequestHeader("content-type","application/json");
                else if(typeof config.data === "string")
                    xhr.setRequestHeader("content-type","application/x-www-form-urlencoded");
                xhr.send(JSON.stringify(config.data));
            }else{
                xhr.send();
            }

        })
    }
    // dispatchRequest 發送請求,undefined 用來補位
    var chain = [dispatchRequest, undefined];
    // 遍歷實例對象的請求攔截器
    this.interceptors.request.handlers.forEach(interceptor=>{
        // 將請求攔截器壓入數組的最前面
        chain.unshift(interceptor.fulfilled,interceptor.rejected)
    })
    // 遍歷實例對象的響應攔截器
    this.interceptors.response.handlers.forEach(interceptor=>{
        // 將請求攔截器壓入數組的最後面
        chain.push(interceptor.fulfilled,interceptor.rejected);
    })
    // 創建一個成功的 promise 且成功的值爲合併後的請求配置
    let promise = Promise.resolve(config);

    // 如果鏈條長度不爲 0
    while (chain.length){
        // 依次取出 chain 的回調函數, 並執行
        promise = promise.then(chain.shift(),chain.shift())
    }
    return promise;
}

// axios 本質不是Axios構造函數的實例,而是一個函數對象。函數是request
function createInstance(defaultConfig){
    const context = new Axios(defaultConfig);
    Axios.prototype.request.bind(context);
    // instance 是一個函數。該函數是request,並且內部this指向context.
    var instance = Axios.prototype.request.bind(context);// 等同於上面那行代碼
    // 將Axios的原型方法放置到instance函數屬性中
    Object.keys(Axios.prototype).forEach(method=>{
        instance[method] = Axios.prototype[method].bind(context)
    })
    Object.keys(context).forEach(attr=>{
        instance[attr] = context[attr];
    })
    return instance;
}

export default createInstance;
5、測試
// 兩個請求
axios.interceptors.request.use(config=>{
    console.log("request1");
    return config;
},error=>{
    console.log("request1",error)
    return Promise.reject(error);
})
axios.interceptors.request.use(config=>{
    console.log("request2");
    // throw "異常呀"
    return config;
},error=>{
    return Promise.reject(error);
})

// 兩個響應
axios.interceptors.response.use(res=>{
    console.log("response1");
    return res;
},error=>{

    return Promise.reject(error)
})

axios.interceptors.response.use(res=>{
    console.log("response2",res);
    return res.data;
},error=>{
    return Promise.reject(error)
})

// 發送請求
axios.request({
    url:"data.json",
    method:"post",
    data:{
        a:1,
        b:2
    }
}).then(value=>{
    console.log(11111,value);
}).catch(err=>{
    console.log(err);
})
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章