axios攔截器機(jī)制的實現(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)部維護(hù)了一個攔截器數(shù)組 handlers。
實例上除了前面介紹過的 use() 和 eject() 方法,還有另外 2 個 clear()、forEach() 方法,這 4 個方法本質(zhì)上都是對內(nèi)部 handlers 數(shù)組進(jìn)行操作。
先說 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() 方法進(jìn)行實際請求。
不過,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 中提供了一個中間件機(jī)制稱為“攔截器”,分請求攔截器和響應(yīng)攔截器 2 種。請求攔截器在請求發(fā)起之前執(zhí)行,允許你修改請求配置,執(zhí)行順序與注冊順序相反;響應(yīng)攔截器在請求完成之后執(zhí)行,允許你修改響應(yīng)對象,執(zhí)行順序與注冊順序一樣。
本文帶大家由淺入深地了解了 axios 的攔截器原理,并查看了 axios 完整的請求鏈,最后基于請求鏈結(jié)構(gòu)了解 axios 中拋錯處理場景的底層邏輯。
以上就是axios攔截器機(jī)制的實現(xiàn)原理詳解的詳細(xì)內(nèi)容,更多關(guān)于axios實現(xiàn)攔截器機(jī)制的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
關(guān)于javascript 回調(diào)函數(shù)中變量作用域的討論
關(guān)于回調(diào)函數(shù)中變量作用域的討論精品推薦,大家可以參考下。2009-09-09
JavaScript基于replace+正則實現(xiàn)ES6的字符串模版功能
這篇文章主要介紹了JavaScript基于replace+正則實現(xiàn)ES6的字符串模版功能,結(jié)合實例形式分析了replace結(jié)合正則實現(xiàn)ES6字符串模板功能的具體步驟與相關(guān)操作技巧,需要的朋友可以參考下2017-04-04
JavaScript如何通過userAgent判斷幾個常用瀏覽器詳解
userAgent 屬性是一個只讀的字符串,聲明了瀏覽器用于 HTTP 請求的用戶代理頭的值,這篇文章主要給大家介紹了關(guān)于JavaScript如何通過userAgent判斷幾個常用瀏覽器的相關(guān)資料,需要的朋友可以參考下2021-06-06
Openlayers實現(xiàn)點閃爍擴(kuò)散效果
這篇文章主要為大家詳細(xì)介紹了Openlayers實現(xiàn)點閃爍擴(kuò)散效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-09-09
JavaScript中html畫布的使用與頁面存儲技術(shù)詳解
這篇文章主要介紹了JavaScript中html畫布的使用與頁面存儲技術(shù),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-08-08
JS簡單實現(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ù)
這篇文章主要為大家詳細(xì)介紹了小程序?qū)崿F(xiàn)輪播每次顯示三條數(shù)據(jù),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-06-06
JS實現(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

