axios 是怎麼封裝 HTTP 請求的

前端開發中,經常會遇到發送異步請求的場景。一個功能齊全的 HTTP 請求庫可以大大降低我們的開發成本,提高開發效率。

axios 是怎麼封裝 HTTP 請求的

axios 就是這樣一個 HTTP 請求庫,近年來非常熱門。目前,它在 GitHub 上擁有超過 40,000 的 Star,許多權威人士都推薦使用它。

因此,我們有必要了解下 axios 是如何設計,以及如何實現 HTTP 請求庫封裝的。撰寫本文時,axios 當前版本為 0.18.0,我們以該版本為例,來閱讀和分析部分核心源代碼。axios 的所有源文件都位於 lib 文件夾中,下文中提到的路徑都是相對於 lib 來說的。

本文我們主要討論:

怎樣使用 axios。

axios 的核心模塊(請求、攔截器、撤銷)是如何設計和實現的?

axios 的設計優點是什麼?

如何使用 axios

要理解 axios 的設計,首先需要看一下如何使用 axios。我們舉一個簡單的例子來說明下 axios API 的使用。

發送請求

axios 是怎麼封裝 HTTP 請求的

這是一個官方示例。從上面的代碼中可以看到,axios 的用法與 jQuery 的 ajax 方法非常類似,兩者都返回一個 Promise 對象(在這裡也可以使用成功回調函數,但還是更推薦使用 Promise 或 await),然後再進行後續操作。

這個實例很簡單,不需要我解釋了。我們再來看看如何添加一個攔截器函數。

添加攔截器函數

axios 是怎麼封裝 HTTP 請求的

從上面的代碼,我們可以知道:發送請求之前,我們可以對請求的配置參數(config)做處理;在請求得到響應之後,我們可以對返回數據做處理。當請求或響應失敗時,我們還能指定對應的錯誤處理函數。

撤銷 HTTP 請求

在開發與搜索相關的模塊時,我們經常要頻繁地發送數據查詢請求。一般來說,當我們發送下一個請求時,需要撤銷上個請求。因此,能撤銷相關請求功能非常有用。axios 撤銷請求的示例代碼如下:

axios 是怎麼封裝 HTTP 請求的

從上例中可以看到,在 axios 中,使用基於 CancelToken 的撤銷請求方案。然而,該提案現已撤回,詳情如 點這裡。具體的撤銷請求的實現方法,將在後面的源代碼分析的中解釋。

axios 核心模塊的設計和實現

通過上面的例子,我相信每個人都對 axios 的使用有一個大致的瞭解了。下面,我們將根據模塊分析 axios 的設計和實現。下面的圖片,是我在本文中會介紹到的源代碼文件。如果您感興趣,最好在閱讀時克隆相關的代碼,這能加深你對相關模塊的理解。

HTTP 請求模塊

請求模塊的代碼放在了 core/dispatchRequest.js 文件中,這裡我只展示了一些關鍵代碼來簡單說明:

axios 是怎麼封裝 HTTP 請求的

上面的代碼中,我們能夠知道 dispatchRequest 方法是通過 config.adapter ,獲得發送請求模塊的。我們還可以通過傳遞,符合規範的適配器函數來替代原來的模塊(一般來說,我們不會這樣做,但它是一個鬆散耦合的擴展點)。

在 defaults.js 文件中,我們可以看到相關適配器的選擇邏輯——根據當前容器的一些獨特屬性和構造函數,來確定使用哪個適配器。

axios 是怎麼封裝 HTTP 請求的

axios 中的 XHR 模塊相對簡單,它是對 XMLHTTPRequest 對象的封裝,這裡我就不再解釋了。有興趣的同學,可以自己閱讀源源碼看看,源碼位於 adapters/xhr.js 文件中。

攔截器模塊

現在讓我們看看 axios 是如何處理,請求和響應攔截器函數的。這就涉及到了 axios 中的統一接口 ——request 函數

axios 是怎麼封裝 HTTP 請求的

這個函數是 axios 發送請求的接口。因為函數實現代碼相當長,這裡我會簡單地討論相關設計思想:

chain 是一個執行隊列。隊列的初始值是一個攜帶配置(config)參數的 Promise 對象。

在執行隊列中,初始函數 dispatchRequest 用來發送請求,為了與 dispatchRequest對應,我們添加了一個 undefined。添加 undefined 的原因是需要給 Promise 提供成功和失敗的回調函數,從下面代碼裡的 promise = promise.then(chain.shift(), chain.shift()); 我們就能看出來。因此,函數 dispatchRequest 和 undefiend 可以看成是一對函數。

在執行隊列 chain 中,發送請求的 dispatchReqeust 函數處於中間位置。它前面是請求攔截器,使用 unshift 方法插入;它後面是響應攔截器,使用 push 方法插入,在 dispatchRequest 之後。需要注意的是,這些函數都是成對的,也就是一次會插入兩個。

瀏覽上面的 request 函數代碼,我們大致知道了怎樣使用攔截器。下一步,來看看怎樣撤銷一個 HTTP 請求。

撤銷請求模塊

與撤銷請求相關的模塊位於 Cancel/ 文件夾下,現在我們來看下相關核心代碼。

首先,我們來看下基礎 Cancel 類。它是一個用來記錄撤銷狀態的類,具體代碼如下:

axios 是怎麼封裝 HTTP 請求的

使用 CancelToken 類時,需要向它傳遞一個 Promise 方法,用來實現 HTTP 請求的撤銷,具體代碼如下:

axios 是怎麼封裝 HTTP 請求的

,讓我們簡要地討論一下相關的實現邏輯:

在需要撤銷的請求中,調用 CancelToken 類的 source 方法類進行初始化,會得到一個包含 CancelToken 類實例 A 和 cancel 方法的對象。

當 source 方法正在返回實例 A 的時候,一個處於 pending 狀態的 promise 對象初始化完成。在將實例 A 傳遞給 axios 之後,promise 就可以作為撤銷請求的觸發器使用了。

當調用通過 source 方法返回的 cancel 方法後,實例 A 中 promise 狀態從 pending 變成 fulfilled,然後立即觸發 then 回調函數。於是 axios 的撤銷方法——request.abort() 被觸發了。

axios 這樣設計的好處是什麼?

發送請求函數的處理邏輯

如前幾章所述,axios 不將用來發送請求的 dispatchRequest 函數看做一個特殊函數。實際上,dispatchRequest 會被放在隊列的中間位置,以便保證隊列處理的一致性和代碼的可讀性。

適配器的處理邏輯

在適配器的處理邏輯上,http 和 xhr 模塊(一個是在 Node.js 中用來發送請求的,一個是在瀏覽器裡用來發送請求的)並沒有在 dispatchRequest 函數中使用,而是各自作為單獨的模塊,默認通過 defaults.js 文件中的配置方法引入的。因此,它不僅確保了兩個模塊之間的低耦合,而且還為將來的用戶提供了定製請求發送模塊的空間。

撤銷 HTTP 請求的邏輯

在撤銷 HTTP 請求的邏輯中,axios 設計使用 Promise 來作為觸發器,將 resolve 函數暴露在外面,並在回調函數里使用。它不僅確保了內部邏輯的一致性,而且還確保了在需要撤銷請求時,不需要直接更改相關類的樣例數據,以避免在很大程度上入侵其他模塊。

總結

本文詳細介紹了 axios 的用法、設計思想和實現方法。在閱讀之後,您可以瞭解 axios 的設計,並瞭解模塊的封裝和交互。

本文只介紹了 axios 的核心模塊,如果你對其他模塊代碼感興趣,可以到 GitHub 上查看。

(完)

鏈接:https://juejin.im/post/5d906269f265da5ba7451b02


分享到:


相關文章: