一比一還原axios原始碼(五)—— 攔截器
上一篇,我們擴充套件了Axios,構建了一個Axios類,然後通過這個Axios工廠類,建立真正的axios例項。那麼今天,我們來實現下Axios的攔截器也就是interceptors。我們來簡單看下Axios的interceptors的API:
首先我們來看,axios上有一個interceptors屬性,該屬性上還有兩個屬性,分別對應request和response,並且都有一個一樣的use方法,該方法目前有兩個引數,分別對應著Promise中的resolve和reject。
另外,你還可以通過對應攔截器的eject方法,移除某個攔截器。
最後,我們還可以通過配置第三個引數,確定執行攔截器的條件、是否非同步等。最後的最後,我們還需要知道攔截器的執行順序,我們先來看一段程式碼:
axios.interceptors.request.use((config) => { config.headers.test += "1"; return config; }); axios.interceptors.request.use((config) => { config.headers.test += "2"; return config; }); axios.interceptors.request.use((config) => { config.headers.test += "3"; return config; }); axios.interceptors.response.use((res)=> { res.data += "1"; return res; }); let c5 = axios.interceptors.response.use((res) => { res.data += "2"; return res; }); axios.interceptors.response.use((res) => { res.data += "3"; return res; }); axios.interceptors.response.eject(c5); axios({ url: "/c5/get", method: "get", headers: { test:"", }, }).then((res) => { console.log(res.data); });
這是我們最終demo裡的程式碼,它的結果是什麼樣子呢?我得在這裡就給出大家答案,不然有個核心的點大家可能就不理解了。其中request的header中的tes的值是321,列印的response的結果是13。OK,依照此我們可以得出結論,就是越靠近請求的攔截器越先執行,什麼意思呢?就是我們文件流中寫在後面的請求攔截器最先執行,寫在前面的響應攔截器最先執行。它是一種以中心向外散射的一種模型。
那麼我們接下來看怎麼來實現這個攔截器吧:
"use strict"; import utils from "./../utils"; function InterceptorManager() { this.handlers = []; } /** * Add a new interceptor to the stack * * @param {Function} fulfilled The function to handle `then` for a `Promise` * @param {Function} rejected The function to handle `reject` for a `Promise` * * @return {Number} An ID used to remove interceptor later */ InterceptorManager.prototype.use = function use(fulfilled, rejected, options) { this.handlers.push({ fulfilled: fulfilled, rejected: rejected, synchronous: options ? options.synchronous : false, runWhen: options ? options.runWhen : null, }); return this.handlers.length - 1; }; /** * Remove an interceptor from the stack * * @param {Number} id The ID that was returned by `use` */ InterceptorManager.prototype.eject = function eject(id) { if (this.handlers[id]) { this.handlers[id] = null; } }; /** * Iterate over all the registered interceptors * * This method is particularly useful for skipping over any * interceptors that may have become `null` calling `eject`. * * @param {Function} fn The function to call for each interceptor */ InterceptorManager.prototype.forEach = function forEach(fn) { utils.forEach(this.handlers, function forEachHandler(h) { if (h !== null) { fn(h); } }); }; export default InterceptorManager;
首先,我們在core資料夾下建立一個InterceptorManager.js,程式碼如上,在檔案內我們構建一個InterceptorManager類,這個類上只有一個數組作為儲存具體攔截器的容器。
然後呢,我們在它的原型上掛載一個use方法,這個前面說過了,就是要把具體的攔截器放置到容器內,以待最後的使用,其中放置的是一個包含了resolve和reject函式以及兩個引數的物件,這個方法返回了一個對應攔截器在容器內的下標作為id。
再然後呢,就是一個eject方法,使用use方法中返回的下標,直接設定為null即可,提問!為啥這裡不直接移除(splice啥的)容器內的攔截器,而是把對應位置的攔截器設定為null呢?
最後,我們提供一個forEach方法,迴圈執行容器內的攔截器即可。那麼到現在為止,整個攔截器管理類就實現了。下面我們看看如何使用。
Axios.prototype.request = function (configOrUrl, config) { if (typeof configOrUrl === "string") { if (!config) { config = {}; } config.url = configOrUrl; } else { config = configOrUrl; } // 請求攔截器呼叫鏈 var requestInterceptorChain = []; // 是否同步 var synchronousRequestInterceptors = true; // 通過攔截器的forEach方法,通過回撥函式的方式,把所有的請求攔截放到requestInterceptorChain數組裡 this.interceptors.request.forEach(function unshiftRequestInterceptors( interceptor ) { if ( // 判斷下如果runWhen是false就return掉了 typeof interceptor.runWhen === "function" && interceptor.runWhen(config) === false ) { return; } // 判斷是否是同步執行 synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous; // 把兩個回撥函式放到陣列的頭部 // 注意這裡不是unshift一個數組,而是獨立的,就是這樣[interceptor.fulfilled,interceptor.rejected] // [3,2,1] requestInterceptorChain.unshift( interceptor.fulfilled, interceptor.rejected ); }); // 響應攔截器呼叫鏈 var responseInterceptorChain = []; // response這個比較簡單,直接push進陣列就完事了 this.interceptors.response.forEach(function pushResponseInterceptors( interceptor ) { responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected); }); // 定一個promise變數,後面用 var promise; // 如果不是同步的 if (!synchronousRequestInterceptors) { var chain = [dispatchRequest, undefined]; // 這塊呢,就把整個requestInterceptorChain放到chain的前面 Array.prototype.unshift.apply(chain, requestInterceptorChain); // 這個就是把responseInterceptorChain放到[requestInterceptorChain,chain]後面 chain = chain.concat(responseInterceptorChain); // 額外要說的是到了這裡,這個chain陣列是什麼樣的呢 // 我們列印下,以我們之前的例子程式碼為例: // 它實際上是這樣的[fn,undefined,fn,undefined,fn,undefined,fn,undefined,fn,undefined,fn,undefined] // 具體點,[requestInterceptorChain,chain,responseInterceptorChain] // 再具體點:[requestResolve3,undefined,requestResolve2,undefined,requestResolve1,undefined,dispatchRequest, undefined,responseResolve1,undefined,responseResolve3,undefined] console.log(chain, "chian"); // 這塊可能就優點疑惑了,首先promise變數變成了一個已經resolved的Promise,resolve出去的就是config配置 promise = Promise.resolve(config); while (chain.length) { // 所以這裡的then裡面就是這樣(resolve,reject) // 注意then方法的第二個引數就是reject的。 // 換句話說,這裡就形成了一個一個的鏈式呼叫,源頭是一個已經resolved的promise。 promise = promise.then(chain.shift(), chain.shift()); } // 返回咯 return promise; } // 那如果是同步的話,走下面的程式碼 // 很簡單,就是同步執行罷了,我就不說了哦。 var newConfig = config; while (requestInterceptorChain.length) { var onFulfilled = requestInterceptorChain.shift(); var onRejected = requestInterceptorChain.shift(); try { // 新的config就是onFulfilled同步函式執行的結果,一步一步往下傳 newConfig = onFulfilled(newConfig); } catch (error) { onRejected(error); break; } } // 執行dispatchRequest返回個promise,dispatchRequest本身就會返回promise,對吧? try { promise = dispatchRequest(newConfig); } catch (error) { return Promise.reject(error); } // 迴圈執行responseInterceptorChain鏈。 while (responseInterceptorChain.length) { promise = promise.then( responseInterceptorChain.shift(), responseInterceptorChain.shift() ); } // 返回,結束 return promise; };
上面是完整的request方法的註釋,還算清晰,大家也可以去gitHub上檢視。那,簡單回顧下,整個執行的核心其實分為了同步和非同步,但是其實整體的程式碼都不復雜,就是呼叫的時候會稍微繞一點。requestInterceptorChain通過unshift後新增的就變成的陣列的頭部,先新增的就變成了陣列的尾部。通過while迴圈,每次都shift出去對應的回撥函式並執行返回promise,這是非同步的做法,同步的做法就比較簡單,同步執行requestInterceptorChain,然後在呼叫request的時候,返回promise,包括後面的responseInterceptorChain也是promise,因為最後要丟擲promise供axios例項使用。
好了,今天的邏輯稍微複雜些,但是本身並不是很難,例子已經在gitHub上了,大家可以親自去體驗下。