axios 源碼系列之攔截器的實現

axios 源碼系列之攔截器的實現

小蚊子
高級前端工程師前端

咱們在使用 Axios 的過程當中,或多或少地要用到它的攔截器,例如要實現:ios

數據轉換;
添加額外的數據;
輸出或上報接口的請求時間、失敗率等數據;
這些需求,使用攔截器就能很是容易地實現。那麼 axios 的攔截器怎麼使用,內部又是怎麼實現的,這篇文章讓咱們一探究竟。axios

1. 攔截器的使用

在 axios 中,攔截器分爲請求攔截器和響應攔截器。顧名思義,請求攔截器是在發出請求以前按照順序執行的,響應攔截器是在收到響應以後(不管接口返回的是否成功)按照順序執行的。數組

若是咱們要統計每一個接口的耗時,能夠先在請求攔截器中添加一個時間戳,在響應攔截器中減去這個時間戳,就是這個請求的完整耗時:promise

// 獲取當前時間
const getTime = () => {
    if (typeof performance?.now === "function") {
        return window.performance.now();
    }
    return Date.now();
};
// 接口上報
const reportCgi = (response, config) => {
    // 響應失敗時response爲空
    const { config: conf } = response || { config };
    // 在響應攔截器中計算這個請求的耗時
    console.log("response", conf.url, getTime() - conf.requestime);
};
axios.interceptors.request.use((config) => {
    // 在請求攔截器中添加發起請求的時間
    return { ...config, ...{ requesttime: getTime() } };
});
axios.interceptors.response.use(
    (response) => {
        reportCgi(response);
        return response;
    },
    (error) => {
        reportCgi(error.response, error.config);
        return error;
    }
);

同時,咱們還能添加多個請求攔截器和響應攔截器:markdown

axios.interceptors.request.use((config) => {
    // 這裏假設要先獲取一個token
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve({ ...config, ...{ token: Math.random() } });
        }, 500);
    });
});
axios.interceptors.request.use((config) => {
    // 在請求攔截器中添加發起請求的時間
    return { ...config, ...{ requesttime: getTime() } };
});

除此以外,axios 的攔截器還能作不少事情,如輸出請求 log 和響應 log,方便在移動端進行調試;上報接口的統計數據等。前端工程師

2. 攔截器是怎麼實現的

攔截器在咱們進行接口請求時,很是的方便。那麼它內部是如何實現的呢?如何維護多個攔截器並按照順序執行的呢?dom

2.1 攔截器的實現

這裏的關鍵文件就是 InterceptorManager.js,這裏的代碼也比較少,咱們一點一點地看它是怎麼實現的:ide

var utils = require("./../utils");

function InterceptorManager() {
    // 存儲全部的攔截器,但請求攔截器和響應攔截器是分開的
    this.handlers = [];
}
/**
 * 添加攔截器
 * fulfilled: 成功時執行的,在Promise.resolve中
 * rejected: 失敗時執行的,在Promise.reject中
 *
 * 返回當前添加的攔截器的ID,用於清除這個攔截器
 */
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
    // 把傳入的在resolve和reject中要執行的方法添加到數組中
    this.handlers.push({
        fulfilled: fulfilled,
        rejected: rejected,
    });
    return this.handlers.length - 1;
};

/**
 * 根據id請求攔截器
 *
 * id: 剛纔use方法返回的那個數據
 */
InterceptorManager.prototype.eject = function eject(id) {
    if (this.handlers[id]) {
        this.handlers[id] = null;
    }
};

/**
 * 迭代全部的攔截器
 *
 * 這裏會跳過以前使用eject方法設置爲null的攔截器
 *
 * @param {Function} fn 對全部攔截器都執行的一個方法
 */
InterceptorManager.prototype.forEach = function forEach(fn) {
    utils.forEach(this.handlers, function forEachHandler(h) {
        if (h !== null) {
            fn(h);
        }
    });
};

module.exports = InterceptorManager;

InterceptorManager 維護着 handlers 裏面全部的攔截器,對外提供了 3 個方法:學習

  • use: 添加攔截器,接受 2 個參數,一個是 Promise 成功時執行的,第 2 個時 Promise 失敗時執行的;
  • eject: 根據 id 清除這個攔截器;
  • forEach: 循環全部的攔截器,並跳過全部爲空的攔截器;
    InterceptorManager 並不區分是請求攔截器仍是響應攔截器,它只是維護他本身的一組攔截器罷了。若建立多個對象,便可分別維護各自的攔截器。
function Axios(instanceConfig) {
    this.defaults = instanceConfig;
    this.interceptors = {
        request: new InterceptorManager(), // 請求攔截器
        response: new InterceptorManager(), // 響應攔截器
    };
}

將攔截器添加到 interceptors 的 request 和 response 兩個屬性中後,咱們就能夠像上面的那樣調用 use 方法添加攔截器了。request 中維護的是請求攔截器,response 中維護的是響應攔截器。

2.2 將攔截器串聯起來

建立一個chain數組,把全部的攔截器都放進去。咱們首先把真正請求接口的方法放進去:

// dispatchRequest 用於請求數據,這裏咱們先展現無論怎麼實現的
// 這裏把 dispatchRequest 也當作攔截器添加到隊列中
// 每2個是一組,前面用於Promise.resolve, 後面的1個用戶Promise.reject
var chain = [dispatchRequest, undefined];

而後把請求攔截器放到 chain 的前面,由於咱們要在發起請求以前先執行請求攔截器:

// 攔截器調用forEach方法,把每個請求攔截器都添加到chain的前面
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    // 每2個是一組,前面用於Promise.resolve, 後面的1個用戶Promise.reject
    // 由此也能看到,越是後添加的請求攔截器,越會是先執行
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
});

再把響應攔截器方法 chain 的後面,由於咱們要在收到響應以後才執行響應攔截器:

// 攔截器調用forEach方法,把每個響應攔截器都添加到chain的後面
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    // 響應攔截器按照順序執行
    chain.push(interceptor.fulfilled, interceptor.rejected);
});

如今已經把全部的請求攔截器、數據請求和響應攔截器都串聯起來了:
axios 源碼系列之攔截器的實現

而後依次執行就能夠:

// 把config初始化爲一個Promise對象,方便後面的使用
var promise = Promise.resolve(config);

while (chain.length) {
    // 依次取出執行resolve和reject方法
    // 將執行後的結果傳給下一個攔截器
    promise = promise.then(chain.shift(), chain.shift());
}

攔截器的功能就實現啦。

3. 總結

咱們學習了 axios 中攔截器的思路,也能夠在本身實現的一些功能組件中,使用這種機制,方便更多功能的擴展。

相關文章
相關標籤/搜索