小蚊子
高級前端工程師前端
咱們在使用 Axios 的過程當中,或多或少地要用到它的攔截器,例如要實現:ios
數據轉換;
添加額外的數據;
輸出或上報接口的請求時間、失敗率等數據;
這些需求,使用攔截器就能很是容易地實現。那麼 axios 的攔截器怎麼使用,內部又是怎麼實現的,這篇文章讓咱們一探究竟。axios
在 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,方便在移動端進行調試;上報接口的統計數據等。前端工程師
攔截器在咱們進行接口請求時,很是的方便。那麼它內部是如何實現的呢?如何維護多個攔截器並按照順序執行的呢?dom
這裏的關鍵文件就是 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 個方法:學習
function Axios(instanceConfig) { this.defaults = instanceConfig; this.interceptors = { request: new InterceptorManager(), // 請求攔截器 response: new InterceptorManager(), // 響應攔截器 }; }
將攔截器添加到 interceptors 的 request 和 response 兩個屬性中後,咱們就能夠像上面的那樣調用 use 方法添加攔截器了。request 中維護的是請求攔截器,response 中維護的是響應攔截器。
建立一個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); });
如今已經把全部的請求攔截器、數據請求和響應攔截器都串聯起來了:
而後依次執行就能夠:
// 把config初始化爲一個Promise對象,方便後面的使用 var promise = Promise.resolve(config); while (chain.length) { // 依次取出執行resolve和reject方法 // 將執行後的結果傳給下一個攔截器 promise = promise.then(chain.shift(), chain.shift()); }
攔截器的功能就實現啦。
咱們學習了 axios 中攔截器的思路,也能夠在本身實現的一些功能組件中,使用這種機制,方便更多功能的擴展。