Axios 源碼解讀 —— request 篇

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)境時使用 nodehttp 模塊構(gòu)建網(wǎng)絡(luò)請求检吆。

今天,我們圍繞著 axios 的源碼實現(xiàn)進行解讀程储,解讀完成后蹭沛,再實現(xiàn)一個簡易的 axios 庫。

我們先來看看 axios 庫的項目目錄結(jié)構(gòu)章鲤。(如下圖)

image

從上圖可以得到兩個信息:

  1. axios 的核心文件是 lib/axios致板,所以我們?nèi)绻魂P(guān)注 axios 運行時的話,只需要看 lib 這個目錄下的文件即可咏窿。
  2. axios 運行只依賴一個第三方庫 follow-redirects斟或,這個庫是用于處理 HTTP 重定向請求的,axios 的默認(rèn)行為是跟隨重定向的集嵌,可以猜測是用這個庫來做重定向跟隨的萝挤。 —— 如果你不想要自動跟隨重定向,需要顯式聲明 maxRedirects=0根欧。

我在百度一直沒找到 axios 的官方文檔怜珍,所以這里貼一份 axios 官方文檔,大家可以參考使用凤粗。

lib/axios

我們打開 lib/axios.js 文件看看酥泛。(如下圖)

image

重點關(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)容緣由了构回。(如下圖)

image

我們也能看出 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。(如下圖)

image

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)雅展东,我會重點介紹赔硫。

相對比較簡單的部分我直接用表格介紹(如下)

image
行數(shù) 描述
32 ~ 41 判斷首個參數(shù),組裝 config 配置盐肃,禁止無 url 的請求
46 ~ 52 設(shè)置請求方法爪膊,如果未聲明則使用默認(rèn)配置的請求方法,如果未設(shè)置默認(rèn)的請求方法砸王,則使用 get 請求

下面我們要著重介紹一下 攔截器 的處理推盛,我們先看 請求攔截器

image

這里有兩個文檔中未說明的參數(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í)行。

這也就意味著 請求攔截器 對同一份配置的修改若专,后面加的攔截器是無法覆蓋前置攔截器的许蓖。

我們看看下面這個請求攔截器案例就知道了。

image

后設(shè)置的攔截器看起來并未生效调衰,看過源碼我們就知道了膊爪,其實是執(zhí)行順序?qū)е碌摹?/p>

axios 這么設(shè)計的原因可能是防止 請求攔截器 濫用導(dǎo)致配置被后續(xù)處理人覆蓋『坷颍—— 但這點沒有在文檔說明米酬,如果正好碰上這種場景,難免會造成一些困惑趋箩。

響應(yīng)攔截器 的處理就簡單多了赃额,相信我應(yīng)該不用多做解釋了。(如下圖)

image

稍微值得注意的是叫确,攔截器 將成功處理和錯誤處理都添加到了內(nèi)部攔截器數(shù)組中跳芳,也就是說數(shù)組內(nèi)部是這樣的:

['攔截器成功處理處理函數(shù)', '攔截器出錯處理函數(shù)', '攔截器成功處理處理函數(shù)', '攔截器出錯處理函數(shù)', ...]

了解這個數(shù)據(jù)結(jié)構(gòu),對最后這一段核心代碼的實現(xiàn)理解是有幫助的(如下圖)

image

我們需要對每一行代碼進行逐行解析:

行數(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 鼓勵一下吧肖抱!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末备典,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子意述,更是在濱河造成了極大的恐慌提佣,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件荤崇,死亡現(xiàn)場離奇詭異拌屏,居然都是意外死亡,警方通過查閱死者的電腦和手機天试,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門槐壳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來然低,“玉大人喜每,你說我怎么就攤上這事■ㄈ粒” “怎么了带兜?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長吨灭。 經(jīng)常有香客問我刚照,道長,這世上最難降的妖魔是什么喧兄? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任无畔,我火速辦了婚禮,結(jié)果婚禮上吠冤,老公的妹妹穿的比我還像新娘浑彰。我一直安慰自己,他們只是感情好拯辙,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布郭变。 她就那樣靜靜地躺著,像睡著了一般涯保。 火紅的嫁衣襯著肌膚如雪诉濒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天夕春,我揣著相機與錄音未荒,去河邊找鬼。 笑死及志,一個胖子當(dāng)著我的面吹牛茄猫,可吹牛的內(nèi)容都是我干的狈蚤。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼划纽,長吁一口氣:“原來是場噩夢啊……” “哼脆侮!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起勇劣,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤靖避,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后比默,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體幻捏,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年命咐,在試婚紗的時候發(fā)現(xiàn)自己被綠了篡九。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡醋奠,死狀恐怖榛臼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情窜司,我是刑警寧澤沛善,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站塞祈,受9級特大地震影響金刁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜议薪,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一尤蛮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧斯议,春花似錦产捞、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至艇搀,卻和暖如春尿扯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背焰雕。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工衷笋, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人矩屁。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓辟宗,卻偏偏與公主長得像爵赵,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子泊脐,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容