Axios 是一個基于 promise 網(wǎng)絡(luò)請求庫,作用于 node.js
和瀏覽器中。 它是 isomorphic 的(即同一套代碼可以運行在瀏覽器和 node.js
中)雄驹。在服務(wù)端它使用原生 node.js
http
模塊, 而在客戶端 (瀏覽端) 則使用 XMLHttpRequests
。
從 Axios 的官方介紹可以得知略板,這是一個可以同時運行在瀏覽器客戶端 + Node 服務(wù)端的網(wǎng)絡(luò)請求庫牧嫉,在瀏覽器運行時,使用 XMLHttpRequests
構(gòu)建請求业扒,在 Node
環(huán)境時使用 node
的 http
模塊構(gòu)建網(wǎng)絡(luò)請求检吆。
今天,我們圍繞著 axios
的源碼實現(xiàn)進行解讀程储,解讀完成后蹭沛,再實現(xiàn)一個簡易的 axios
庫。
我們先來看看 axios
庫的項目目錄結(jié)構(gòu)章鲤。(如下圖)
從上圖可以得到兩個信息:
- axios 的核心文件是
lib/axios
致板,所以我們?nèi)绻魂P(guān)注axios
運行時的話,只需要看lib
這個目錄下的文件即可咏窿。 - axios 運行只依賴一個第三方庫
follow-redirects
斟或,這個庫是用于處理HTTP
重定向請求的,axios
的默認(rèn)行為是跟隨重定向的集嵌,可以猜測是用這個庫來做重定向跟隨的萝挤。 —— 如果你不想要自動跟隨重定向,需要顯式聲明maxRedirects=0
根欧。
我在百度一直沒找到 axios
的官方文檔怜珍,所以這里貼一份 axios 官方文檔,大家可以參考使用凤粗。
lib/axios
我們打開 lib/axios.js
文件看看酥泛。(如下圖)
重點關(guān)注這幾行核心就可以了。
行數(shù) | 描述 |
---|---|
第 26 行 |
文檔中的 axios.create 調(diào)用的是 createInstance 函數(shù),這個函數(shù)將會新建一個 Axios 實例 |
第 34 行 |
新建了一個默認(rèn)的 axios 實例 |
第 37 ~ 52 行 |
默認(rèn)的 axios 實例添加了大量的屬性和方法 |
第 57 行 |
將默認(rèn)的 axios 實例導(dǎo)出 |
Axios
接下來柔袁,我們來看看 Axios
類呆躲,這是 axios
源碼最核心的部分,位于 lib/core/Axios.js
捶索。
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
Axios
接收配置插掂,將 instanceConfig
配置存在 axios.defaults
屬性中,用于后續(xù)的請求腥例。
同時辅甥,通過 InterceptorManager
來管理請求攔截器和響應(yīng)攔截器,在后面我們會展開聊聊這個攔截管理器 —— InterceptorManager
燎竖。
axios
默認(rèn)的實例將會使用lib/defaults.js
中的配置進行創(chuàng)建璃弄。
從 Axios
這個設(shè)置,我們就知道文檔中關(guān)于修改配置的這部分內(nèi)容緣由了构回。(如下圖)
我們也能看出 Axios
實例是 axios
對于 網(wǎng)絡(luò)請求-默認(rèn)配置
的最小單位谢揪,而不存在所有實例共享的一套 “全局默認(rèn)配置”
。
但是我們可以通過兩個方法來實現(xiàn)捐凭。
其中一個方法就是我們寫兩套配置拨扶,一套全局默認(rèn)配置,一套各實例的個性化配置茁肠,在創(chuàng)建 axios
實例的時候做手動合并患民。
當(dāng)然,還有個更聰明的方法垦梆,我們先來看看之前 P1 的 createInstance
方法匹颤。
function createInstance(defaultConfig) {
var context = new Axios(defaultConfig);
var instance = bind(Axios.prototype.request, context);
//...
instance.create = function create(instanceConfig) {
// 這里通過閉包繼承了 defaultConfig 配置,新創(chuàng)建的實例會繼承原實例的配置
return createInstance(mergeConfig(defaultConfig, instanceConfig));
};
return instance;
}
從代碼中可以看出托猩,createInstance
方法內(nèi)部通過閉包繼承了 defaultConfig
配置印蓖,新創(chuàng)建的實例會繼承原實例的配置。
這樣的話京腥,我們還可以通過先用一套全局默認(rèn)配置創(chuàng)建一個 axios
實例赦肃,然后再使用這個 axios
實例,調(diào)用 axios/instance.create
方法創(chuàng)建其他的 axios
實例公浪,這樣所有的 axios
實例都可以繼承全局默認(rèn)配置他宛。
攔截管理器 —— InterceptorManager
我們現(xiàn)在來看看 axios
內(nèi)部的攔截管理器 InterceptorManager
。(如下圖)
InterceptorManager
的總體實現(xiàn)還是挺簡單的欠气,內(nèi)部有個 handlers
數(shù)組厅各,存放了所有通過 use
方法注冊的攔截器。
use
方法返回了 handlers.length - 1
作為攔截器 id预柒,在調(diào)用 eject
方法時队塘,會將對應(yīng) ID 下標(biāo)的攔截器設(shè)置為 null
袁梗。
forEach
方法對所有的攔截器方法進行遍歷,執(zhí)行傳入的 fn
回調(diào)函數(shù)憔古,將攔截器作為參數(shù)傳入 fn
中遮怜。
從這里可以看出,axios
內(nèi)部的職責(zé)劃分還是比較清晰的投放,InterceptorManager
只負(fù)責(zé)收集管理攔截器,而不關(guān)心攔截器的執(zhí)行邏輯适贸。
不過我感覺 forEach
方法設(shè)計的有些冗余灸芳,如果是我來設(shè)計的話,我可能只會暴露一個 getter
方法讓外部獲取 handlers
拜姿。這里作者的設(shè)計可能有一些別的考慮烙样,我暫時還沒有想到。
request
接下來蕊肥,我們看看 Axios
類的核心方法谒获,也就是 request
方法,axios
通過 request
方法來發(fā)起真實網(wǎng)絡(luò)請求壁却。
這一段代碼比較長批狱,我會對這一大段代碼進行逐行解析。request
方法中對于攔截器和請求的處理非常優(yōu)雅展东,我會重點介紹赔硫。
相對比較簡單的部分我直接用表格介紹(如下)
行數(shù) | 描述 |
---|---|
第 32 ~ 41 行 |
判斷首個參數(shù),組裝 config 配置盐肃,禁止無 url 的請求 |
第 46 ~ 52 行 |
設(shè)置請求方法爪膊,如果未聲明則使用默認(rèn)配置的請求方法,如果未設(shè)置默認(rèn)的請求方法砸王,則使用 get 請求 |
下面我們要著重介紹一下 攔截器
的處理推盛,我們先看 請求攔截器
。
這里有兩個文檔中未說明的參數(shù)谦铃,這里我們做一下解釋:
- 第
58
行:使用use
注冊攔截器時耘成,第三個參數(shù)中的options.runWhen
方法將會先被調(diào)用,如果該方法返回false
驹闰,則跳過該請求攔截器凿跳。 - 第
62
行:使用use
注冊攔截器時,第三個參數(shù)中的options.synchronous
參數(shù)將顯式聲明該攔截器為同步
疮方,否則默認(rèn)為異步
攔截器控嗜,將會通過promise
進行調(diào)用。 —— 其實我覺得這個參數(shù)沒什么意義了骡显,統(tǒng)一異步
就好了疆栏≡啵可能作者還考慮了其他的某些同步場景,我暫時還沒有想到壁顶。
重點注意:第 64
行使用了 unshift
方法將 請求攔截器
按注冊的逆序添加到 requestInterceptorChain
中珠洗,供后續(xù)執(zhí)行。
這也就意味著 請求攔截器
對同一份配置的修改若专,后面加的攔截器是無法覆蓋前置攔截器的许蓖。
我們看看下面這個請求攔截器案例就知道了。
后設(shè)置的攔截器看起來并未生效调衰,看過源碼我們就知道了膊爪,其實是執(zhí)行順序?qū)е碌摹?/p>
axios
這么設(shè)計的原因可能是防止 請求攔截器
濫用導(dǎo)致配置被后續(xù)處理人覆蓋『坷颍—— 但這點沒有在文檔說明米酬,如果正好碰上這種場景,難免會造成一些困惑趋箩。
而 響應(yīng)攔截器
的處理就簡單多了赃额,相信我應(yīng)該不用多做解釋了。(如下圖)
稍微值得注意的是叫确,攔截器
將成功處理和錯誤處理都添加到了內(nèi)部攔截器數(shù)組中跳芳,也就是說數(shù)組內(nèi)部是這樣的:
['攔截器成功處理處理函數(shù)', '攔截器出錯處理函數(shù)', '攔截器成功處理處理函數(shù)', '攔截器出錯處理函數(shù)', ...]
了解這個數(shù)據(jù)結(jié)構(gòu),對最后這一段核心代碼的實現(xiàn)理解是有幫助的(如下圖)
我們需要對每一行代碼進行逐行解析:
行數(shù) | 描述 |
---|---|
第 74 行 |
判斷是否為異步請求攔截器(默認(rèn):是) |
第 75 行 |
聲明 chain 數(shù)組竹勉,數(shù)組第一個元素是發(fā)起請求的方法(可以簡單理解為 fetch 方法)筛严,第二個元素是為了 82 行湊數(shù)的 undefined
|
第 77 行 |
將所有的 請求攔截器 添加到 chain 的開頭位置 |
第 78 行 |
將所有的 響應(yīng)攔截器 添加到 chain 的尾部位置 |
第 80 ~ 83 行 |
用 config 構(gòu)建第一個 Promise ,然后按順序依次執(zhí)行 chain —— 請求攔截器 -> 真實請求 -> 響應(yīng)攔截器 饶米,每次執(zhí)行傳入的就是 成功處理函數(shù) (作為 resolve ) 和 失敗處理函數(shù) (作為 reject ) |
最后一段 chain
的執(zhí)行桨啃,非常優(yōu)雅的闡述了 axios
內(nèi)部的工作流程,也就是 請求攔截器
-> 真實請求
-> 響應(yīng)攔截器
這一套核心工作流檬输。
建議大家可以仔細(xì)再看看最后一段函數(shù)的處理照瘾,仔細(xì)品一品。
下面還有一段關(guān)于
同步請求攔截器
的處理丧慈,基本上是大同小異的析命,感興趣的童鞋可以自行閱讀一下。
小結(jié)
好了逃默,到這里鹃愤,axios
的基本結(jié)構(gòu)和核心工作流程就解析完了。
下一章完域,我會針對 真實請求
—— dispatchRequest
進行詳細(xì)解析软吐,請大家繼續(xù)關(guān)注。
最后一件事
如果您已經(jīng)看到這里了吟税,希望您還是點個贊再走吧~
您的點贊是對作者的最大鼓勵凹耙,也可以讓更多人看到本篇文章姿现!
如果覺得本文對您有幫助,請幫忙在 github 上點亮 star
鼓勵一下吧肖抱!