axios攔截器機(jī)制的實(shí)現(xiàn)原理詳解
axios 攔截器簡(jiǎn)介
axios 中的攔截器分“請(qǐng)求攔截器”和“響應(yīng)攔截器”。
請(qǐng)求攔截器是在請(qǐ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); });
請(qǐng)求攔截器是通過(guò) axios.interceptors.request.use() 方法注冊(cè)的,你可以多次調(diào)用,這樣可以同時(shí)設(shè)置多個(gè)攔截器。
請(qǐng)求攔截器的作用允許你在請(qǐng)求正式發(fā)起前,對(duì)請(qǐng)求配置信息做最后的統(tǒng)一修改。
當(dāng)然,攔截器也支持移除。
const interceptorID = axios.interceptors.request.use(function () {/*...*/}); axios.interceptors.request.eject(interceptorID);
axios.interceptors.request.use() 會(huì)返回當(dāng)前攔截器在內(nèi)部 ID 值(一個(gè)數(shù)值),你可以通過(guò) axios.interceptors.eject(interceptorID) 移除這個(gè)攔截器。
響應(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)攔截器是通過(guò) axios.interceptors.response.use() 方法注冊(cè)的,你可以多次調(diào)用,這樣可以同時(shí)設(shè)置多個(gè)攔截器。
響應(yīng)攔截器不僅作用于有響應(yīng)的返回(比如 2xx、 4xx、5xx),對(duì)于像網(wǎng)絡(luò)請(qǐng)求意外中斷(比如超時(shí))、跨域請(qǐng)求錯(cuò)誤(CORS)這樣沒(méi)有響應(yīng)的請(qǐng)求同樣適用。
響應(yīng)攔截器的作用是允許你在響應(yīng)在給用戶處理之前,先對(duì)響應(yīng)結(jié)果(比如 reponse.data)做統(tǒng)一處理。
同時(shí),你可以通過(guò) interceptors.response.eject(interceptorID) 移除特定的響應(yīng)攔截器。
Axios 實(shí)例
你可以將 axios 庫(kù)暴露出來(lái)的 axios
對(duì)象近似看成是內(nèi)部 Axios 類的實(shí)例。
Axios 類實(shí)例在創(chuàng)建的時(shí)候,會(huì)掛載一個(gè)對(duì)象屬性 interceptors,這個(gè)對(duì)象又包含 request 和 response 2 個(gè)屬性,它們都是 InterceptorManager 類的實(shí)例對(duì)象。
// /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)部維護(hù)了一個(gè)攔截器數(shù)組 handlers。
實(shí)例上除了前面介紹過(guò)的 use() 和 eject() 方法,還有另外 2 個(gè) clear()、forEach() 方法,這 4 個(gè)方法本質(zhì)上都是對(duì)內(nèi)部 handlers 數(shù)組進(jìn)行操作。
先說(shuō) 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() 方法是用來(lái)注冊(cè)攔截器的,也就是向 handlers 數(shù)組添加一個(gè)成員。
use() 接收 3 個(gè)參數(shù),第一個(gè)參數(shù)和第二個(gè)參數(shù)分別用來(lái)定義攔截器正常操作和異常捕獲的邏輯;最后一個(gè)選項(xiàng)參數(shù) options 是可選的,定位攔截器行為,不太常用,為避免行文冗長(zhǎng),不會(huì)介紹。
use() 會(huì)返回當(dāng)前攔截器在內(nèi)部 handlers 數(shù)組的索引值,是后續(xù)用來(lái)移除攔截器的標(biāo)識(shí)(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); } }); }
用來(lái)遍歷所有攔截器,不過(guò)這里會(huì)做為空判斷,確保會(huì)忽略被移除的攔截。
到這里我們就講清楚攔截器實(shí)例了。
那么 axios 是在發(fā)請(qǐng)求前后是如何讓攔截器生效的呢?
攔截器實(shí)現(xiàn)原理
在上文的學(xué)習(xí)中,我們了解到 axios 請(qǐng)求會(huì)由內(nèi)部 axios._request() 方法處理,而在 axios._request() 方法內(nèi)部,會(huì)調(diào)用 dispatchRequest() 方法進(jìn)行實(shí)際請(qǐng)求。
不過(guò),dispatchRequest() 方法執(zhí)行前后,其實(shí)是有攔截器執(zhí)行邏輯的,不過(guò)之前沒(méi)說(shuō),現(xiàn)在我們就來(lái)看下。
在 axios 內(nèi)部,實(shí)際的請(qǐng)求會(huì)包含攔截器的執(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 個(gè)變量 requestInterceptorChain 和 responseInterceptorChain 來(lái)分別收集使用 axios.interceptors.request.use() 和 axios.interceptors.response.use() 注冊(cè)的攔截器。
收集的過(guò)程采用的是 interceptor 實(shí)例的 forEach() 方法,這樣就能過(guò)濾掉被移除的攔截器。
這里有 2 個(gè)點(diǎn)值得注意:
- 攔截器推入 2 個(gè) Chain 的方式是 .unshift/push(interceptor.fulfilled, interceptor.rejected) 而非 .unshift/push(interceptor),其實(shí) 2 種方式都行,不過(guò) axios 選擇了前者
- 另外,請(qǐng)求攔截器的推入方式是使用 unshift()(而非響應(yīng)攔截器的 push()),就表示后注冊(cè)的請(qǐng)求攔截器會(huì)先執(zhí)行,這一點(diǎn)需要注意
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
拼接請(qǐng)求鏈
收集好攔截器后,就著手拼接請(qǐng)求鏈了。
// /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;
請(qǐng)求鏈 chain 以請(qǐng)求攔截器開(kāi)始、實(shí)際請(qǐng)求居中、響應(yīng)攔截器最后的方式拼接。
整個(gè) Promise 鏈以請(qǐng)求配置 config 作為起點(diǎn)執(zhí)行,到 dispatchRequest() 之后返回 response,因此后續(xù)響應(yīng)攔截器的入?yún)⒆兂?response 了。
當(dāng)我們以 axios.get('httpstat.us/200').then(… 請(qǐng)求時(shí),對(duì) axios 來(lái)說(shuō),完整的請(qǐng)求鏈?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 的完整請(qǐng)求鏈結(jié)構(gòu)。
由請(qǐng)求鏈結(jié)構(gòu)看錯(cuò)誤處理
明白了 axios 請(qǐng)求鏈結(jié)構(gòu),我們就能明白 axios 中拋錯(cuò)場(chǎng)景的底層邏輯。
第一個(gè)請(qǐng)求攔截器出錯(cuò)
通過(guò)前面的學(xué)習(xí),我們已經(jīng)知道請(qǐng)求攔截器的執(zhí)行跟注冊(cè)順序是相反的,第一個(gè)請(qǐng)求攔截器會(huì)最后執(zhí)行。
當(dāng)?shù)谝粋€(gè)請(qǐng)求攔截器出錯(cuò)時(shí),由于 dispatchRequest() 部分并沒(méi)有處理異常的部分。
.then( dispatchRequest.bind(this), )
所以,錯(cuò)誤會(huì)直接穿透到第一個(gè)響應(yīng)攔截器,交由對(duì)應(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)
效果如下:
由于第一個(gè)響應(yīng)攔截器的 onReject 函數(shù)沒(méi)有返回值,所以 .then(console.log) 最終打印出來(lái)的時(shí) undefined。
最后一個(gè)請(qǐng)求攔截器出錯(cuò)
最后一個(gè)請(qǐng)求攔截器會(huì)最先執(zhí)行,它的錯(cuò)誤會(huì)被倒數(shù)第二個(gè)請(qǐng)求攔截器的 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)
效果如下:
最后一個(gè)請(qǐng)求攔截器"request interceptor 2"出錯(cuò)后,會(huì)由"request interceptor 1"的 rejected 函數(shù)處理。
由于"request interceptor 1"的 rejected 函數(shù)沒(méi)有返回值,導(dǎo)致傳遞給 dispatchRequest() 實(shí)際執(zhí)行時(shí)接收到的 config 是 undefined,內(nèi)部就會(huì)報(bào)錯(cuò)了。
dispatchRequest() 的錯(cuò)誤會(huì)由 "response interceptor 1"的 rejected 函數(shù)處理,由于"response interceptor 1"的 rejected 函數(shù)沒(méi)有返回值,導(dǎo)致 .then(console.log) 打印結(jié)果 undefined。
axios 請(qǐng)求出錯(cuò)
axios 請(qǐng)求如果出錯(cuò),會(huì)先經(jīng)過(guò)響應(yīng)攔截器處理。
axios.interceptors.response.use( undefined, (error) => { console.log('[error] response interceptor 1', error) } ) axios.get('https://httpstat.uss/200').then(console.log)
效果如下:
我們請(qǐng)求了一個(gè)無(wú)效地址,結(jié)果報(bào)錯(cuò)被響應(yīng)攔截器處理,由于沒(méi)有返回值,導(dǎo)致 .then(console.log) 打印結(jié)果 undefined。
響應(yīng)攔截器出錯(cuò)
響應(yīng)攔截器之后就是用戶自己的處理邏輯了,如果是在響應(yīng)攔截器中出錯(cuò),那么就會(huì)錯(cuò)誤就會(huì)落入用戶的 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 中提供了一個(gè)中間件機(jī)制稱為“攔截器”,分請(qǐng)求攔截器和響應(yīng)攔截器 2 種。請(qǐng)求攔截器在請(qǐng)求發(fā)起之前執(zhí)行,允許你修改請(qǐng)求配置,執(zhí)行順序與注冊(cè)順序相反;響應(yīng)攔截器在請(qǐng)求完成之后執(zhí)行,允許你修改響應(yīng)對(duì)象,執(zhí)行順序與注冊(cè)順序一樣。
本文帶大家由淺入深地了解了 axios 的攔截器原理,并查看了 axios 完整的請(qǐng)求鏈,最后基于請(qǐng)求鏈結(jié)構(gòu)了解 axios 中拋錯(cuò)處理場(chǎng)景的底層邏輯。
以上就是axios攔截器機(jī)制的實(shí)現(xiàn)原理詳解的詳細(xì)內(nèi)容,更多關(guān)于axios實(shí)現(xiàn)攔截器機(jī)制的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
關(guān)于javascript 回調(diào)函數(shù)中變量作用域的討論
關(guān)于回調(diào)函數(shù)中變量作用域的討論精品推薦,大家可以參考下。2009-09-09JavaScript基于replace+正則實(shí)現(xiàn)ES6的字符串模版功能
這篇文章主要介紹了JavaScript基于replace+正則實(shí)現(xiàn)ES6的字符串模版功能,結(jié)合實(shí)例形式分析了replace結(jié)合正則實(shí)現(xiàn)ES6字符串模板功能的具體步驟與相關(guān)操作技巧,需要的朋友可以參考下2017-04-04JavaScript如何通過(guò)userAgent判斷幾個(gè)常用瀏覽器詳解
userAgent 屬性是一個(gè)只讀的字符串,聲明了瀏覽器用于 HTTP 請(qǐng)求的用戶代理頭的值,這篇文章主要給大家介紹了關(guān)于JavaScript如何通過(guò)userAgent判斷幾個(gè)常用瀏覽器的相關(guān)資料,需要的朋友可以參考下2021-06-06JavaScript閉包和作用域鏈的定義實(shí)現(xiàn)
這篇文章主要為大家介紹了JavaScript閉包和作用域鏈的定義與實(shí)現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05Openlayers實(shí)現(xiàn)點(diǎn)閃爍擴(kuò)散效果
這篇文章主要為大家詳細(xì)介紹了Openlayers實(shí)現(xiàn)點(diǎn)閃爍擴(kuò)散效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-09-09JavaScript中html畫布的使用與頁(yè)面存儲(chǔ)技術(shù)詳解
這篇文章主要介紹了JavaScript中html畫布的使用與頁(yè)面存儲(chǔ)技術(shù),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-08-08JS簡(jiǎn)單實(shí)現(xiàn)滑動(dòng)加載數(shù)據(jù)的方法示例
這篇文章主要介紹了JS簡(jiǎn)單實(shí)現(xiàn)滑動(dòng)加載數(shù)據(jù)的方法,涉及javascript事件響應(yīng)及頁(yè)面元素屬性動(dòng)態(tài)操作相關(guān)技巧,需要的朋友可以參考下2017-10-10小程序?qū)崿F(xiàn)輪播每次顯示三條數(shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了小程序?qū)崿F(xiàn)輪播每次顯示三條數(shù)據(jù),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06