上一章我們介紹了 Axios 原始碼解讀 —— request 篇,這一章我們來介紹 Axios 實際發起網路請求的部分吧,也就是 dispatchRequest
方法。
dispatchRequest
這個方法定義也比較簡單(如下圖)
第 29 行 —— 取消請求
我們來逐行解析每一行程式碼所做的事情吧,首先是第 29 行的取消請求。(如下)
throwIfCancellationRequested(config);
這個動作不僅僅在發起正式請求前做了一次,而且在請求成功和請求失敗時都做了一次。
只要是被 cancel
的請求,都是不會進入到成功或失敗回撥處理中的。(如下圖)
而 throwIfCancellationRequested
函式所做的事情,就是檢測該請求是否被取消,如果被取消則丟擲一個錯誤,並阻止程式碼繼續向下執行。(如下)
function throwIfCancellationRequested(config) {
if (config.cancelToken) {
// 檢測請求是否被取消,然後決定是否丟擲錯誤
config.cancelToken.throwIfRequested();
}
if (config.signal && config.signal.aborted) {
// 丟擲錯誤
throw new Cancel('canceled');
}
}
當然,整套 CancelToken
的實現還是有一些複雜的(複雜在回撥處理),如果有人感興趣的話,可以單獨講講這一部分的處理,這裡就先不做展開了。
第 35 ~ 40 行 —— 處理請求 data
config.data = transformData.call(
config,
config.data,
config.headers,
config.transformRequest
);
這裡用到了一個 transformData
方法,主要的作用是合併 data
,並且可以使用配置的 transformRequest
方法進行合併(如下圖)。
而 transformData
方法就是遍歷傳入的 transformRequest
方法,將 data
和 headers
作為 transformRequest
的入參,然後將返回結果複製給 config.data
,作為最後請求要傳送的 data
內容。
第 43 ~ 54 行 —— 合併請求 headers
config.headers = utils.merge(
config.headers.common || {},
config.headers[config.method] || {},
config.headers
);
utils.forEach(
['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
function cleanHeaderConfig(method) {
delete config.headers[method];
}
);
上面的程式碼主要做了兩件事情:
- 第一件事情就是將通用的
headers
(common headers
)和對應方法的headers
以及原headers
做了一個合併; - 第二件事情就是在合併完成後,將多餘的
headers
配置刪除。
第 56 ~ 58 行 —— 發起真實請求
var adapter = config.adapter || defaults.adapter;
return adapter(config).then(function onAdapterResolution(response) {
// ...
});
這裡使用了配置的 adapter
,這其實就是發起請求的方法。
比如兩個預設的請求方法,在瀏覽器端執行時使用的是 XMLHttpRequest
,在 Node
端執行時使用的是 http
模組。
知道這一項配置可以做什麼呢?
你可以在這裡傳入一個自定義的請求方法,例如在客戶端使用 fetch
封裝,而不是 XMLHttpRequest
。
你也可以在這裡傳入一個你封裝好的 mock
方法,返回的都是本地 json
資料,用於 mock
資料,這樣你就不用額外搭建一個 mock
服務啦。
......
言歸正傳,我們還是來看看 axios
預設的兩個 adapter
吧,本文會重點講解客戶端 adapter
—— xhrAdapter
。
客戶端 adapter —— xhrAdapter
我們按照慣例,對客戶端 adapter
—— xhrAdapter
逐行進行解析。
發起請求前的準備工作
行數 | 描述 |
---|---|
第 16 ~ 18 行 | 收集請求資料資訊(requestData )、請求頭資訊(requestHeaders )、返回體型別(responseType ) |
第 19 ~ 28 行 | 對 cancelToken 做處理,在請求完成的時候取消這個訂閱,釋放記憶體;還有對 signal 的處理,這個 signal 在文件中已經看不到了,應該也是用於 abort 請求的。 |
第 30 ~ 32 行 | 對於 FormData 的請求移除 Content-Type 請求頭,讓瀏覽器自動設定請求頭。 |
設定鑑權資訊
首先在第 34
行,建立了一個 XMLHttpRequest
例項,用於後續的網路請求。
然後在第 37 ~ 41
行,設定了用於 HTTP basic authentication
鑑權的資訊,從這一段我們可以學到簡單的 HTTP basic authentication
鑑權知識。
首先對 password
進行 encodeURLComponent
轉義編碼,然後再將使用者名稱與密碼按照規則拼接後,使用了 btoa
將 使用者名稱與密碼
轉成了 base64
編碼。
如果以後你要自己做這類事情的話,可以再翻到這一章節找到這部分的程式碼內容,抄一遍。
拼接請求 url
首先是用 buildFullPath
方法拼接了完整的請求 url
。(如下圖)
可以看到,該方法對絕對路徑的 url
(isAbsoluteURL()
) 是不會拼接 baseURL
的。
然後,axios
還用 buildURL
方法將 params
引數拼接到了 url
中 —— 也就是我們常說的 query
引數。(如下)
function buildURL(url, params, paramsSerializer) {
/*eslint no-param-reassign:0*/
if (!params) {
return url;
}
var serializedParams;
if (paramsSerializer) {
serializedParams = paramsSerializer(params);
} else if (utils.isURLSearchParams(params)) {
serializedParams = params.toString();
} else {
// ...
serializedParams = parts.join('&');
}
if (serializedParams) {
// ...
// 在這裡,將處理後的引數作為 query 查詢引數拼接到 url 中
url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams;
}
return url;
};
這也是為什麼使用 axios
時,GET
方法的引數要設定在 params
欄位中。
然後,使用 request.open
方法初始化了一個請求。
響應回撥函式
接下來就是比較核心的響應回撥函式了(如下圖)
行數 | 描述 |
---|---|
第 54 行 | 獲取所有響應頭 |
第 55 行 | 獲取響應內容 |
第 57 ~ 64 行 | 設定 promise resolve 內容,就是你經常 console.log 出來的那些東西,你應該很眼熟 |
設定其他回撥函式
後面基本上都是設定 XMLHttpRequest
物件的一些回撥函式了。(如下圖)
比較晚入行接觸前端的,可能對 XMLHttpRequest
例項這些事件所做的事情不太清楚,可以參考一下 XMLHttpRequest 文件。
最後,傳送這個請求。(如下)
request.send(requestData);
回到 dispatchRequest
從上面可以看出,最後 dispatchRequest
呼叫 adapter
後,拿到了下面這些資料。
{
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config: config,
request: request
};
然後我們來看看 dispatchRequest
內部是如何處理這一段資料的吧。(如下圖)
行數 | 描述 |
---|---|
第 59/72 行 | 處理被 CancelToken 取消的請求,如果請求已取消則不繼續向下執行 |
第 62~67 行 | 將響應結果通過 transformResponse 進行轉換,得到處理後的響應資料 |
第 69 行 | 將響應結果返回 |
這樣一來,整個 axios
的請求流程就梳理清晰了,我們畫一張流程圖來梳理一下。(如下圖)
這裡對 Node
端的 adapter
就不進行展開講解了,和客戶端的差異主要在於下面幾點:
- 使用了
Node
的http
模組發起請求。 - 可以通過
proxy
設定代理伺服器,請求將會傳送到代理伺服器。
小結
好了,我們對 axios
原始碼的解讀就到這裡為止了。
可以看出,axios
雖然是目前最流行的、比較強大的網路請求框架,但是原始碼看起來還是比較清爽易讀的,建議大家可以自己按照文章的思路去看看。
下一章,我們會實現一個簡易的 axios
框架,包含 axios
的一些核心功能,將 axios
原始碼解讀系列收官。
最後一件事
如果您已經看到這裡了,希望您還是點個贊再走吧~
您的點贊是對作者的最大鼓勵,也可以讓更多人看到本篇文章!
如果覺得本文對您有幫助,請幫忙在 github 上點亮 star
鼓勵一下吧!