axios攔截器機制的實現(xiàn)原理詳解
axios 攔截器簡介
axios 中的攔截器分“請求攔截器”和“響應(yīng)攔截器”。
請求攔截器是在請求正式發(fā)起前調(diào)用的。
// Add a request interceptor axios.interceptors.request.use(function (config) { // Do something before request is sent return config; }, function (error) { // Do something with request error return Promise.reject(error); });
請求攔截器是通過 axios.interceptors.request.use() 方法注冊的,你可以多次調(diào)用,這樣可以同時設(shè)置多個攔截器。
請求攔截器的作用允許你在請求正式發(fā)起前,對請求配置信息做最后的統(tǒng)一修改。
當(dāng)然,攔截器也支持移除。
const interceptorID = axios.interceptors.request.use(function () {/*...*/}); axios.interceptors.request.eject(interceptorID);
axios.interceptors.request.use() 會返回當(dāng)前攔截器在內(nèi)部 ID 值(一個數(shù)值),你可以通過 axios.interceptors.eject(interceptorID) 移除這個攔截器。
響應(yīng)攔截器與此同理。
// Add a response interceptor axios.interceptors.response.use(function (response) { // Any status code that lie within the range of 2xx cause this function to trigger // Do something with response data return response; }, function (error) { // Any status codes that falls outside the range of 2xx cause this function to trigger // Do something with response error return Promise.reject(error); });
響應(yīng)攔截器是通過 axios.interceptors.response.use() 方法注冊的,你可以多次調(diào)用,這樣可以同時設(shè)置多個攔截器。
響應(yīng)攔截器不僅作用于有響應(yīng)的返回(比如 2xx、 4xx、5xx),對于像網(wǎng)絡(luò)請求意外中斷(比如超時)、跨域請求錯誤(CORS)這樣沒有響應(yīng)的請求同樣適用。
響應(yīng)攔截器的作用是允許你在響應(yīng)在給用戶處理之前,先對響應(yīng)結(jié)果(比如 reponse.data)做統(tǒng)一處理。
同時,你可以通過 interceptors.response.eject(interceptorID) 移除特定的響應(yīng)攔截器。
Axios 實例
你可以將 axios 庫暴露出來的 axios
對象近似看成是內(nèi)部 Axios 類的實例。
Axios 類實例在創(chuàng)建的時候,會掛載一個對象屬性 interceptors,這個對象又包含 request 和 response 2 個屬性,它們都是 InterceptorManager 類的實例對象。
// /v1.6.8/lib/core/Axios.js class Axios { constructor(instanceConfig) { this.defaults = instanceConfig; this.interceptors = { request: new InterceptorManager(), response: new InterceptorManager() }; }
攔截器類
InterceptorManager 類也就是攔截器類了,它的大概結(jié)構(gòu)如下。
// /v1.6.8/lib/core/InterceptorManager.js class InterceptorManager { constructor() { this.handlers = []; } // Add a new interceptor to the stack use(fulfilled, rejected, options) {/* ... */} // Remove an interceptor from the stack eject(id) {/* ... */} // Clear all interceptors from the stack clear() {/* ... */} // Iterate over all the registered interceptors forEach(fn) {/* ... */} }
InterceptorManager 內(nèi)部維護了一個攔截器數(shù)組 handlers。
實例上除了前面介紹過的 use() 和 eject() 方法,還有另外 2 個 clear()、forEach() 方法,這 4 個方法本質(zhì)上都是對內(nèi)部 handlers 數(shù)組進行操作。
先說 use()。
// /v1.6.8/lib/core/InterceptorManager.js#L18 use(fulfilled, rejected, options) { this.handlers.push({ fulfilled, rejected, synchronous: options ? options.synchronous : false, runWhen: options ? options.runWhen : null }); return this.handlers.length - 1; }
use() 方法是用來注冊攔截器的,也就是向 handlers 數(shù)組添加一個成員。
use() 接收 3 個參數(shù),第一個參數(shù)和第二個參數(shù)分別用來定義攔截器正常操作和異常捕獲的邏輯;最后一個選項參數(shù) options 是可選的,定位攔截器行為,不太常用,為避免行文冗長,不會介紹。
use() 會返回當(dāng)前攔截器在內(nèi)部 handlers 數(shù)組的索引值,是后續(xù)用來移除攔截器的標(biāo)識(interceptorID)。
再是 eject(id)。
// /v1.6.8/lib/core/InterceptorManager.js#L35-L39 eject(id) { if (this.handlers[id]) { this.handlers[id] = null; } }
就是清空內(nèi)部 handlers 數(shù)組。
最后是 forEach(fn)。
// /v1.6.8/lib/core/InterceptorManager.js#L62-L68 forEach(fn) { this.handlers.forEach(function forEachHandler(h) { if (h !== null) { fn(h); } }); }
用來遍歷所有攔截器,不過這里會做為空判斷,確保會忽略被移除的攔截。
到這里我們就講清楚攔截器實例了。
那么 axios 是在發(fā)請求前后是如何讓攔截器生效的呢?
攔截器實現(xiàn)原理
在上文的學(xué)習(xí)中,我們了解到 axios 請求會由內(nèi)部 axios._request() 方法處理,而在 axios._request() 方法內(nèi)部,會調(diào)用 dispatchRequest() 方法進行實際請求。
不過,dispatchRequest() 方法執(zhí)行前后,其實是有攔截器執(zhí)行邏輯的,不過之前沒說,現(xiàn)在我們就來看下。
在 axios 內(nèi)部,實際的請求會包含攔截器的執(zhí)行邏輯,以便做中間操作。
收集攔截器
在此之前,我們就要先收集攔截器。
// /v1.6.8/lib/core/Axios.js#L115-L131 const requestInterceptorChain = []; this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected); }); const responseInterceptorChain = []; this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected); });
這里使用了 2 個變量 requestInterceptorChain 和 responseInterceptorChain 來分別收集使用 axios.interceptors.request.use() 和 axios.interceptors.response.use() 注冊的攔截器。
收集的過程采用的是 interceptor 實例的 forEach() 方法,這樣就能過濾掉被移除的攔截器。
這里有 2 個點值得注意:
- 攔截器推入 2 個 Chain 的方式是 .unshift/push(interceptor.fulfilled, interceptor.rejected) 而非 .unshift/push(interceptor),其實 2 種方式都行,不過 axios 選擇了前者
- 另外,請求攔截器的推入方式是使用 unshift()(而非響應(yīng)攔截器的 push()),就表示后注冊的請求攔截器會先執(zhí)行,這一點需要注意
axios.interceptors.request.use( (config) => { console.log('request interceptor 1') return config; } ) axios.interceptors.request.use( (config) => { console.log('request interceptor 2') return config; } ) axios.get('https://httpstat.us/200') // request interceptor 2 // request interceptor 1
拼接請求鏈
收集好攔截器后,就著手拼接請求鏈了。
// /v1.6.8/lib/core/Axios.js#L133-L150 let promise; let i = 0; let len; const chain = [ ...requestInterceptorChain, ...[dispatchRequest.bind(this), undefined], ...responseInterceptorChain ] len = chain.length; promise = Promise.resolve(config); while (i < len) { promise = promise.then(chain[i++], chain[i++]); } return promise;
請求鏈 chain 以請求攔截器開始、實際請求居中、響應(yīng)攔截器最后的方式拼接。
整個 Promise 鏈以請求配置 config 作為起點執(zhí)行,到 dispatchRequest() 之后返回 response,因此后續(xù)響應(yīng)攔截器的入?yún)⒆兂?response 了。
當(dāng)我們以 axios.get('httpstat.us/200').then(… 請求時,對 axios 來說,完整的請求鏈?zhǔn)牵?/p>
Promise.resolve({ url: 'https://httpstat.us/200' }) .then( requestInterceptorFulfilled, requestInterceptorRejected, ) .then( dispatchRequest.bind(this), ) .then( responseInterceptorFulfilled, responseInterceptorRejected, ) .then( console.log )
由此,我們便講完了 axios 的攔截器的執(zhí)行原理以及 axios 的完整請求鏈結(jié)構(gòu)。
由請求鏈結(jié)構(gòu)看錯誤處理
明白了 axios 請求鏈結(jié)構(gòu),我們就能明白 axios 中拋錯場景的底層邏輯。
第一個請求攔截器出錯
通過前面的學(xué)習(xí),我們已經(jīng)知道請求攔截器的執(zhí)行跟注冊順序是相反的,第一個請求攔截器會最后執(zhí)行。
當(dāng)?shù)谝粋€請求攔截器出錯時,由于 dispatchRequest() 部分并沒有處理異常的部分。
.then( dispatchRequest.bind(this), )
所以,錯誤會直接穿透到第一個響應(yīng)攔截器,交由對應(yīng)的 rejected 函數(shù)處理。
axios.interceptors.request.use( (config) => { console.log('request interceptor 1') throw new Error('Oops 1') } ) axios.interceptors.response.use( undefined, (error) => { console.log('[error] response interceptor 1', error) } ) axios.get('https://httpstat.us/200').then(console.log)
效果如下:
由于第一個響應(yīng)攔截器的 onReject 函數(shù)沒有返回值,所以 .then(console.log) 最終打印出來的時 undefined。
最后一個請求攔截器出錯
最后一個請求攔截器會最先執(zhí)行,它的錯誤會被倒數(shù)第二個請求攔截器的 rejected 函數(shù)處理。
axios.interceptors.request.use( undefined, (error) => { console.log('[error] request interceptor 1', error) } ) axios.interceptors.request.use( (config) => { console.log('request interceptor 2') throw new Error('Oops 2') } ) axios.interceptors.response.use( undefined, (error) => { console.log('[error] response interceptor 1', error) } ) axios.get('https://httpstat.us/200').then(console.log)
效果如下:
最后一個請求攔截器"request interceptor 2"出錯后,會由"request interceptor 1"的 rejected 函數(shù)處理。
由于"request interceptor 1"的 rejected 函數(shù)沒有返回值,導(dǎo)致傳遞給 dispatchRequest() 實際執(zhí)行時接收到的 config 是 undefined,內(nèi)部就會報錯了。
dispatchRequest() 的錯誤會由 "response interceptor 1"的 rejected 函數(shù)處理,由于"response interceptor 1"的 rejected 函數(shù)沒有返回值,導(dǎo)致 .then(console.log) 打印結(jié)果 undefined。
axios 請求出錯
axios 請求如果出錯,會先經(jīng)過響應(yīng)攔截器處理。
axios.interceptors.response.use( undefined, (error) => { console.log('[error] response interceptor 1', error) } ) axios.get('https://httpstat.uss/200').then(console.log)
效果如下:
我們請求了一個無效地址,結(jié)果報錯被響應(yīng)攔截器處理,由于沒有返回值,導(dǎo)致 .then(console.log) 打印結(jié)果 undefined。
響應(yīng)攔截器出錯
響應(yīng)攔截器之后就是用戶自己的處理邏輯了,如果是在響應(yīng)攔截器中出錯,那么就會錯誤就會落入用戶的 catch 處理邏輯。
axios.interceptors.response.use( (response) => { console.log('response interceptor 1') throw new Error('Oops 1') } ) axios.get('https://httpstat.uss/200').catch(err => console.log('err >>>', err))
效果如下:
總結(jié)
axios 中提供了一個中間件機制稱為“攔截器”,分請求攔截器和響應(yīng)攔截器 2 種。請求攔截器在請求發(fā)起之前執(zhí)行,允許你修改請求配置,執(zhí)行順序與注冊順序相反;響應(yīng)攔截器在請求完成之后執(zhí)行,允許你修改響應(yīng)對象,執(zhí)行順序與注冊順序一樣。
本文帶大家由淺入深地了解了 axios 的攔截器原理,并查看了 axios 完整的請求鏈,最后基于請求鏈結(jié)構(gòu)了解 axios 中拋錯處理場景的底層邏輯。
以上就是axios攔截器機制的實現(xiàn)原理詳解的詳細內(nèi)容,更多關(guān)于axios實現(xiàn)攔截器機制的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
關(guān)于javascript 回調(diào)函數(shù)中變量作用域的討論
關(guān)于回調(diào)函數(shù)中變量作用域的討論精品推薦,大家可以參考下。2009-09-09JavaScript基于replace+正則實現(xiàn)ES6的字符串模版功能
這篇文章主要介紹了JavaScript基于replace+正則實現(xiàn)ES6的字符串模版功能,結(jié)合實例形式分析了replace結(jié)合正則實現(xiàn)ES6字符串模板功能的具體步驟與相關(guān)操作技巧,需要的朋友可以參考下2017-04-04JavaScript如何通過userAgent判斷幾個常用瀏覽器詳解
userAgent 屬性是一個只讀的字符串,聲明了瀏覽器用于 HTTP 請求的用戶代理頭的值,這篇文章主要給大家介紹了關(guān)于JavaScript如何通過userAgent判斷幾個常用瀏覽器的相關(guān)資料,需要的朋友可以參考下2021-06-06JavaScript中html畫布的使用與頁面存儲技術(shù)詳解
這篇文章主要介紹了JavaScript中html畫布的使用與頁面存儲技術(shù),本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-08-08JS簡單實現(xiàn)滑動加載數(shù)據(jù)的方法示例
這篇文章主要介紹了JS簡單實現(xiàn)滑動加載數(shù)據(jù)的方法,涉及javascript事件響應(yīng)及頁面元素屬性動態(tài)操作相關(guān)技巧,需要的朋友可以參考下2017-10-10小程序?qū)崿F(xiàn)輪播每次顯示三條數(shù)據(jù)
這篇文章主要為大家詳細介紹了小程序?qū)崿F(xiàn)輪播每次顯示三條數(shù)據(jù),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-06-06JS實現(xiàn)點擊下拉列表文本框中出現(xiàn)對應(yīng)的網(wǎng)址,點擊跳轉(zhuǎn)按鈕實現(xiàn)跳轉(zhuǎn)
這篇文章主要介紹了JS實現(xiàn)點擊下拉列表文本框中出現(xiàn)對應(yīng)的網(wǎng)址,點擊跳轉(zhuǎn)按鈕實現(xiàn)跳轉(zhuǎn),本文給大家分享實例代碼,代碼簡單易懂,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2019-11-11