Axios 原始碼解讀 —— 網路請求篇

曬兜斯發表於2022-01-16

上一章我們介紹了 Axios 原始碼解讀 —— request 篇,這一章我們來介紹 Axios 實際發起網路請求的部分吧,也就是 dispatchRequest 方法。

dispatchRequest

這個方法定義也比較簡單(如下圖)

image

第 29 行 —— 取消請求

我們來逐行解析每一行程式碼所做的事情吧,首先是第 29 行的取消請求。(如下)

throwIfCancellationRequested(config);

這個動作不僅僅在發起正式請求前做了一次,而且在請求成功和請求失敗時都做了一次。

只要是被 cancel 的請求,都是不會進入到成功或失敗回撥處理中的。(如下圖)

image

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 方法進行合併(如下圖)。

image

transformData 方法就是遍歷傳入的 transformRequest 方法,將 dataheaders 作為 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];
  }
);

上面的程式碼主要做了兩件事情:

  • 第一件事情就是將通用的 headerscommon headers)和對應方法的 headers 以及原 headers 做了一個合併;
  • 第二件事情就是在合併完成後,將多餘的 headers 配置刪除。

第 56 ~ 58 行 —— 發起真實請求

var adapter = config.adapter || defaults.adapter;

return adapter(config).then(function onAdapterResolution(response) {
  // ...
});

這裡使用了配置的 adapter,這其實就是發起請求的方法。

image

比如兩個預設的請求方法,在瀏覽器端執行時使用的是 XMLHttpRequest,在 Node 端執行時使用的是 http 模組。

知道這一項配置可以做什麼呢?

你可以在這裡傳入一個自定義的請求方法,例如在客戶端使用 fetch 封裝,而不是 XMLHttpRequest

你也可以在這裡傳入一個你封裝好的 mock 方法,返回的都是本地 json 資料,用於 mock 資料,這樣你就不用額外搭建一個 mock 服務啦。

......

言歸正傳,我們還是來看看 axios 預設的兩個 adapter 吧,本文會重點講解客戶端 adapter —— xhrAdapter

客戶端 adapter —— xhrAdapter

我們按照慣例,對客戶端 adapter —— xhrAdapter 逐行進行解析。

發起請求前的準備工作

image

行數描述
16 ~ 18收集請求資料資訊(requestData)、請求頭資訊(requestHeaders)、返回體型別(responseType
19 ~ 28cancelToken 做處理,在請求完成的時候取消這個訂閱,釋放記憶體;還有對 signal 的處理,這個 signal 在文件中已經看不到了,應該也是用於 abort 請求的。
30 ~ 32對於 FormData 的請求移除 Content-Type 請求頭,讓瀏覽器自動設定請求頭。

設定鑑權資訊

image

首先在第 34 行,建立了一個 XMLHttpRequest 例項,用於後續的網路請求。

然後在第 37 ~ 41 行,設定了用於 HTTP basic authentication 鑑權的資訊,從這一段我們可以學到簡單的 HTTP basic authentication 鑑權知識。

首先對 password 進行 encodeURLComponent 轉義編碼,然後再將使用者名稱與密碼按照規則拼接後,使用了 btoa使用者名稱與密碼 轉成了 base64 編碼。

如果以後你要自己做這類事情的話,可以再翻到這一章節找到這部分的程式碼內容,抄一遍。

拼接請求 url

image

首先是用 buildFullPath 方法拼接了完整的請求 url。(如下圖)

image

可以看到,該方法對絕對路徑的 urlisAbsoluteURL()) 是不會拼接 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 方法初始化了一個請求。

響應回撥函式

接下來就是比較核心的響應回撥函式了(如下圖)

image

行數描述
54獲取所有響應頭
55獲取響應內容
57 ~ 64設定 promise resolve 內容,就是你經常 console.log 出來的那些東西,你應該很眼熟

設定其他回撥函式

後面基本上都是設定 XMLHttpRequest 物件的一些回撥函式了。(如下圖)

image

比較晚入行接觸前端的,可能對 XMLHttpRequest 例項這些事件所做的事情不太清楚,可以參考一下 XMLHttpRequest 文件

最後,傳送這個請求。(如下)

request.send(requestData);

回到 dispatchRequest

從上面可以看出,最後 dispatchRequest 呼叫 adapter 後,拿到了下面這些資料。

{
  data: responseData,
  status: request.status,
  statusText: request.statusText,
  headers: responseHeaders,
  config: config,
  request: request
};

然後我們來看看 dispatchRequest 內部是如何處理這一段資料的吧。(如下圖)

image

行數描述
59/72處理被 CancelToken 取消的請求,如果請求已取消則不繼續向下執行
62~67將響應結果通過 transformResponse 進行轉換,得到處理後的響應資料
69將響應結果返回

這樣一來,整個 axios 的請求流程就梳理清晰了,我們畫一張流程圖來梳理一下。(如下圖)

image

這裡對 Node 端的 adapter 就不進行展開講解了,和客戶端的差異主要在於下面幾點:

  1. 使用了 Nodehttp 模組發起請求。
  2. 可以通過 proxy 設定代理伺服器,請求將會傳送到代理伺服器。

小結

好了,我們對 axios 原始碼的解讀就到這裡為止了。

可以看出,axios 雖然是目前最流行的、比較強大的網路請求框架,但是原始碼看起來還是比較清爽易讀的,建議大家可以自己按照文章的思路去看看。

下一章,我們會實現一個簡易的 axios 框架,包含 axios 的一些核心功能,將 axios 原始碼解讀系列收官。

最後一件事

如果您已經看到這裡了,希望您還是點個贊再走吧~

您的點贊是對作者的最大鼓勵,也可以讓更多人看到本篇文章!

如果覺得本文對您有幫助,請幫忙在 github 上點亮 star 鼓勵一下吧!

相關文章