axios攔截器工作方式及原理源碼解析
axios 攔截器的配置方式
本文所用 axios 版本號為:1.3.2。
axios 中有兩種攔截器:
axios.interceptors.request.use(onFulfilled, onRejected, options):配置請求攔截器。- onFulfilled 方法在發(fā)送請求前執(zhí)行,接收 config 對象,返回一個新的 config 對象,可在此方法內(nèi)修改 config 對象。
- onRejected 方法在 onFulfilled 執(zhí)行錯誤后執(zhí)行,接收 onFulfilled 執(zhí)行后的錯誤對象。
- options 配置參數(shù)
- synchronous:控制請求攔截器是否為異步執(zhí)行,默認(rèn)為 true,每個攔截器都可以單獨(dú)設(shè)置,只要有一個設(shè)置為 false 則為同步執(zhí)行,否則為異步執(zhí)行。
- runWhen:一個方法,接收 config 對象作為參數(shù),在每次調(diào)用設(shè)定的攔截器方法前調(diào)用,返回結(jié)果為 true 時執(zhí)行攔截器方法。
axios.interceptors.response.use(onFulfilled, onRejected, options):配置響應(yīng)攔截器。- onFulfilled 方法在返回響應(yīng)結(jié)果前調(diào)用,接收響應(yīng)對象,返回一個新的響應(yīng)對象,可在此方法內(nèi)修改響應(yīng)對象。
- onRejected 與 options 同
axios.interceptors.request.use。
axios.interceptors.request.use(
function (config) {
return config;
},
function (error) {
return Promise.reject(error);
}
);
axios.interceptors.response.use(
function (response) {
return response;
},
function (error) {
return Promise.reject(error);
}
);
可以添加多個攔截器:
axios.interceptors.request.use(
function (config) {
throw new Error("999");
return config;
},
function (error) {
console.log(1, error);
return Promise.reject(error);
}
);
axios.interceptors.request.use(
function (config) {
throw new Error("888");
return config;
},
function (error) {
console.log(2, error);
return Promise.reject(error);
}
);
axios.interceptors.response.use(
function (response) {
return response;
},
function (error) {
console.log(3, error);
return Promise.reject(error);
}
);
axios.interceptors.response.use(
function (response) {
return response;
},
function (error) {
console.log(4, error);
return Promise.reject(error);
}
);
axios.get("https://www.baidwwu.com").catch((error) => {
console.log(4, error);
});
// 2 Error: 888
// 1 Error: 888
// 3 Error: 888
// 4 Error: 888
// 5 Error: 888
先執(zhí)行請求攔截器,后執(zhí)行響應(yīng)攔截器。
- 設(shè)置多個請求攔截器的情況:后添加的攔截器先執(zhí)行。
- 設(shè)置多個響應(yīng)攔截器的情況:按照設(shè)置順序執(zhí)行。
use() 方法的定義
先來看 use() 方法相關(guān)代碼:
this.interceptors = {
request: new InterceptorManager$1(),
response: new InterceptorManager$1(),
};
var InterceptorManager = (function () {
function InterceptorManager() {
this.handlers = [];
}
// ...
// _createClass 方法可以給對象的原型上添加屬性
_createClass(InterceptorManager, [
{
key: "use",
value: 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;
},
},
{
key: "forEach",
value: function forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
fn(h);
}
});
},
},
// ...
]);
return InterceptorManager;
})();
var InterceptorManager$1 = InterceptorManager;
可以看到 interceptors.request 和 interceptors.response 各自指向 InterceptorManager 的實(shí)例。
InterceptorManager 原型上設(shè)置了 use() 方法,執(zhí)行后給待執(zhí)行隊(duì)列 this.handlers 中添加一條數(shù)據(jù)。
這里利用了 this 的特性來區(qū)分作用域:誰調(diào)用 use() 方法就給誰的 handlers 中添加數(shù)據(jù)。在調(diào)用 use() 方法之后,已經(jīng)將回調(diào)函數(shù)按順序添加到了 handlers 數(shù)組中。
forEach() 方法用來遍歷當(dāng)前作用域 handlers 中不為 null 的元素,在執(zhí)行攔截器時有用到,詳情見下文。
攔截器如何執(zhí)行
攔截器是在調(diào)用了 request() 方法前后執(zhí)行的,先看相關(guān)源碼:
var Axios = (function () {
_createClass(Axios, [
{
key: "request",
value: function request(configOrUrl, config) {
// ...
// 初始化請求攔截器
var requestInterceptorChain = [];
var synchronousRequestInterceptors = true;
this.interceptors.request.forEach(function unshiftRequestInterceptors(
interceptor
) {
if (
typeof interceptor.runWhen === "function" &&
interceptor.runWhen(config) === false
) {
return;
}
synchronousRequestInterceptors =
synchronousRequestInterceptors && interceptor.synchronous;
requestInterceptorChain.unshift(
interceptor.fulfilled,
interceptor.rejected
);
});
// 初始化響應(yīng)攔截器
var responseInterceptorChain = [];
this.interceptors.response.forEach(function pushResponseInterceptors(
interceptor
) {
responseInterceptorChain.push(
interceptor.fulfilled,
interceptor.rejected
);
});
var promise;
var i = 0;
var len;
// 請求攔截器同步執(zhí)行模式
if (!synchronousRequestInterceptors) {
var chain = [dispatchRequest.bind(this), undefined];
chain.unshift.apply(chain, requestInterceptorChain);
chain.push.apply(chain, responseInterceptorChain);
len = chain.length;
promise = Promise.resolve(config);
console.log(11, chain);
while (i < len) {
promise = promise.then(chain[i++]).catch(chain[i++]);
}
return promise;
}
// 請求攔截器異步執(zhí)行模式
len = requestInterceptorChain.length;
var newConfig = config;
i = 0;
while (i < len) {
var onFulfilled = requestInterceptorChain[i++];
var onRejected = requestInterceptorChain[i++];
try {
newConfig = onFulfilled(newConfig);
} catch (error) {
onRejected.call(this, error);
break;
}
}
try {
promise = dispatchRequest.call(this, newConfig);
} catch (error) {
return Promise.reject(error);
}
i = 0;
len = responseInterceptorChain.length;
while (i < len) {
promise = promise.then(
responseInterceptorChain[i++],
responseInterceptorChain[i++]
);
}
return promise;
},
},
]);
})();
上面是相關(guān)的全部代碼,下面進(jìn)行分解。
攔截器回調(diào)方法的添加順序
var requestInterceptorChain = []; // 請求攔截器執(zhí)行鏈
// 是否同步執(zhí)行請求攔截器,每個攔截器都可以單獨(dú)設(shè)置,但是只有所有攔截器都設(shè)置為true才為true
var synchronousRequestInterceptors = true;
this.interceptors.request.forEach(function unshiftRequestInterceptors(
interceptor
) {
// 傳入 runWhen() 方法時,如果 runWhen() 方法返回值為 false 則忽略這個請求攔截器
if (
typeof interceptor.runWhen === "function" &&
interceptor.runWhen(config) === false
) {
return;
}
// 是否同步執(zhí)行
synchronousRequestInterceptors =
synchronousRequestInterceptors && interceptor.synchronous;
// 用 unshift() 添加數(shù)據(jù),后設(shè)置的攔截器在前
requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
});
var responseInterceptorChain = []; // 響應(yīng)攔截器執(zhí)行鏈
this.interceptors.response.forEach(function pushResponseInterceptors(
interceptor
) {
// 響應(yīng)攔截器按順序加在后面
responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
});
interceptors.request.forEach() 的定義在上文提到過,封裝為遍歷自身的 handlers 數(shù)組,與數(shù)組的 forEach 類似,只是過濾了值為 null 的元素。
runWhen() 方法接收 config,返回 boolean,控制是否將當(dāng)前攔截器的回調(diào)函數(shù)添加到執(zhí)行鏈中。
請求攔截器用 unshift() 方法添加,所以后設(shè)置的先執(zhí)行,響應(yīng)攔截器用 push() 方法添加,所以按照設(shè)置順序執(zhí)行。
只要有一個攔截器的 synchronous 設(shè)置為 false,則 synchronousRequestInterceptors 的值為 false。
同步執(zhí)行請求攔截器(順序執(zhí)行)
synchronousRequestInterceptors 為 false 時為同步執(zhí)行,相關(guān)邏輯如下:
var promise;
var i = 0;
var len;
if (!synchronousRequestInterceptors) {
var chain = [dispatchRequest.bind(this), undefined]; // 執(zhí)行鏈
chain.unshift.apply(chain, requestInterceptorChain); // 將請求攔截器添加到請求之前
chain.push.apply(chain, responseInterceptorChain); // 將響應(yīng)攔截器添加到響應(yīng)之后
len = chain.length;
promise = Promise.resolve(config);
while (i < len) {
promise = promise.then(chain[i++], chain[i++]); // 用 Promise 包裝執(zhí)行鏈
}
return promise;
}
dispatchRequest() 是發(fā)送請求的方法。
同步執(zhí)行模式下,會將執(zhí)行鏈中的所有方法用 Promise 進(jìn)行封裝,前一個方法執(zhí)行完畢后將其返回值作為參數(shù)傳遞給下一個方法。
這里的 chain 其實(shí)就是所有攔截器方法與請求方法合并而成的執(zhí)行鏈,等價于: [...requestInterceptorChain, dispatchRequest.bind(this), ...responseInterceptorChain]。
從一個例子來看 chain 的成員:
axios.interceptors.request.use(onFulfilled1, onRejected1); axios.interceptors.request.use(onFulfilled2, onRejected2); axios.interceptors.response.use(onFulfilled3, onRejected3); axios.interceptors.response.use(onFulfilled4, onRejected4); axios.get(); // chain: [onFulfilled2, onRejected2, onFulfilled1, onRejected1, dispatchRequest, onFulfilled3, onRejected3, onFulfilled4, onRejected4]
在構(gòu)建 Promise 鏈的時候,一次遍歷中取了兩個方法傳遞給 then():
promise = Promise.resolve(config);
while (i < len) {
promise = promise.then(chain[i++], chain[i++]); // 用 Promise 包裝執(zhí)行鏈
}
return promise;
異步執(zhí)行請求攔截器(同時執(zhí)行)
var promise;
var i = 0;
var len = requestInterceptorChain.length; // 請求執(zhí)行鏈的長度
var newConfig = config;
i = 0;
// 執(zhí)行請求攔截器回調(diào)
while (i < len) {
var onFulfilled = requestInterceptorChain[i++]; // use() 的第一個參數(shù)
var onRejected = requestInterceptorChain[i++]; // use() 的第二個參數(shù)
// 執(zhí)行成功后繼續(xù)執(zhí)行,執(zhí)行失敗后停止執(zhí)行。
try {
newConfig = onFulfilled(newConfig);
} catch (error) {
onRejected.call(this, error);
break;
}
}
// 執(zhí)行發(fā)送請求方法
try {
promise = dispatchRequest.call(this, newConfig);
} catch (error) {
return Promise.reject(error); // 執(zhí)行失敗后退出
}
// 執(zhí)行響應(yīng)攔截器回調(diào)
i = 0;
len = responseInterceptorChain.length;
while (i < len) {
promise = promise.then(
responseInterceptorChain[i++], // use() 的第一個參數(shù)
responseInterceptorChain[i++] // use() 的第二個參數(shù)
);
}
return promise;
dispatchRequest() 是發(fā)送請求的方法。
用 while 循環(huán)遍歷所有的請求攔截器并調(diào)用,將執(zhí)行語句包裹在 try-catch 語句中,只要有一個請求攔截器異常就停止循環(huán)。
可以看到在異步模式下,請求攔截器為異步執(zhí)行,但是不影響發(fā)送請求,而響應(yīng)攔截器還是在請求響應(yīng)后同步執(zhí)行。
Q&A
攔截器是如何工作的
調(diào)用 .request.use() 和 .response.use() 方法時,將傳入的攔截器回調(diào)方法分別存入 請求攔截器回調(diào)數(shù)組 和 響應(yīng)攔截器回調(diào)數(shù)組。
在調(diào)用 .get() 等方法時,將 請求攔截器回調(diào)數(shù)組 和 響應(yīng)攔截器回調(diào)數(shù)組 與發(fā)送請求的方法拼接成一個完整的執(zhí)行鏈,按照同步或異步的模式調(diào)用執(zhí)行鏈中的方法。
響應(yīng)攔截器總是在返回響應(yīng)結(jié)果后按順序執(zhí)行。
請求攔截器根據(jù) synchronous 配置不同,行為有所不同:
- 異步執(zhí)行請求攔截器模式。
- 沒有設(shè)置
synchronous時默認(rèn)為異步執(zhí)行請求攔截器模式,即遍歷執(zhí)行所有的請求攔截器一參回調(diào),執(zhí)行報錯后停止遍歷,并執(zhí)行該攔截器的二參回調(diào)。 - 同步執(zhí)行請求攔截器模式。
- 設(shè)置
synchronous為false時為同步執(zhí)行請求攔截器模式,將執(zhí)行鏈包裝成一個 Promise 鏈順序執(zhí)行。
攔截器的執(zhí)行順序
先執(zhí)行請求攔截器,后執(zhí)行響應(yīng)攔截器。
- 設(shè)置多個請求攔截器的情況:后添加的攔截器先執(zhí)行。
- 設(shè)置多個響應(yīng)攔截器的情況:按照設(shè)置順序執(zhí)行。
同步&異步
計算機(jī)中的同步,指的是現(xiàn)實(shí)中的一步一步(同一時間只能干一件事),異步指的是同時進(jìn)行(同一時間能干多件事)。
參考 npm axios
以上就是axios攔截器工作方式及原理源碼解析的詳細(xì)內(nèi)容,更多關(guān)于axios攔截器工作原理的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue實(shí)現(xiàn)滾動條下滑時隱藏導(dǎo)航欄,上滑時顯示導(dǎo)航欄功能
這篇文章主要介紹了vue實(shí)現(xiàn)滾動條下滑時隱藏導(dǎo)航欄,上滑時顯示導(dǎo)航欄,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-07-07
Vue的Eslint配置文件eslintrc.js說明與規(guī)則介紹
最近在跟著視頻敲項(xiàng)目時,代碼提示出現(xiàn)很多奇奇怪怪的錯誤提示,百度了一下是eslintrc.js文件沒有配置相關(guān)命令,ESlint的語法檢測真的令人抓狂,現(xiàn)在總結(jié)一下這些命令的解釋2020-02-02
element?plus如何為表格某列數(shù)據(jù)文字設(shè)置顏色樣式
實(shí)習(xí)工作需要根據(jù)表格的狀態(tài)字段來設(shè)置列的樣式,所以這篇文章主要給大家介紹了關(guān)于element?plus如何為表格某列數(shù)據(jù)文字設(shè)置顏色樣式的相關(guān)資料,需要的朋友可以參考下2023-10-10
vue觸發(fā)真實(shí)的點(diǎn)擊事件跟用戶行為一致問題
這篇文章主要介紹了vue觸發(fā)真實(shí)的點(diǎn)擊事件跟用戶行為一致問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03
vue中三種插槽(默認(rèn)插槽/具名插槽/作用域插槽)的區(qū)別詳解
默認(rèn)插槽,具名插槽,作用域插槽是vue中常用的三個插槽,這篇文章主要為大家介紹了這三種插槽的使用與區(qū)別,感興趣的小伙伴可以了解一下2023-08-08
Vue3?企業(yè)級組件庫框架搭建?pnpm?monorepo實(shí)戰(zhàn)示例
這篇文章主要為大家介紹了Vue3?企業(yè)級組件庫框架搭建?pnpm?monorepo實(shí)戰(zhàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11

