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