axios 核心源碼解讀

原文連接node

介紹axios

一直在使用axios庫,在享受它帶來的便利的同時,總感受不讀讀它的源碼有點對不起它,恰好網上介紹axios源碼的文章較少,因此寫下這篇文章,權當拋磚引玉。ios

axios是同構的JavaScript的異步請求庫,它能夠在瀏覽器端和NodeJS環境裏使用。git

VueJS的做者尤大也推薦這個工具,它除了異步請求網絡資源功能,還有以下功能:github

  1. 提供代理功能
  2. 提供了攔截器(相似中間件),能夠註冊在請求發出去以前和收到響應以後的操做
  3. 能夠獲取上傳進度和下載進度
  4. 提供的adapter選項能夠模擬響應數據
  5. 自定義引發報錯的響應碼範圍
  6. 提供了取消請求的功能

axios的GitHub地址axios

那麼,它是怎麼辦到的呢?

首先說說爲何它能夠在瀏覽器端和NodeJS環境中使用

在axios中,使用適配器設計模式來屏蔽平臺的差別性,讓使用者能夠在瀏覽器端和NodeJS環境中使用同一套API發起http請求。設計模式

axios的默認配置裏的adapter是經過getDefaultAdapter()方法來獲取的,它的邏輯以下:數組

function getDefaultAdapter() {
  var adapter;
  // Only Node.JS has a process variable that is of [[Class]] process
  if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
    // For node use HTTP adapter
    adapter = require('./adapters/http');
  } else if (typeof XMLHttpRequest !== 'undefined') {
    // For browsers use XHR adapter
    adapter = require('./adapters/xhr');
  }
  return adapter;
}
複製代碼

如上面代碼,經過判斷運行環境的特徵來選擇不一樣的API發起http請求。promise

接下來分別介紹這兩個文件——http和xhr。瀏覽器

http.js

這個文件裏,引用了NodeJS的http和https庫,用於發出http請求,並使用Promise接收請求結果。網絡

代碼的細節不介紹了,就講個大概的思路,咱們都知道發起http請求,最重要的是遵照http協議,書寫正確的請求頭,而axios就是經過傳入config接收使用者的一些定製參數,其中包括請求頭,請求參數等等,而後在內部使用(http/https).request(options, callback)發起http請求。

具體如何整合、處理傳入的參數,還請下載源碼看看。

xhr.js

相似http的邏輯,只不過是調用了WebAPI的XMLHTTPRequest接口發起http請求。

攔截器的實現

axios提供了攔截器的功能,能夠在請求發起前處理傳入的config或者其它操做,也能夠在接收完響應後處理response。

咱們能夠看看Axios的構造函數,很簡單:

function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}
複製代碼

其中的InterceptorManager維護一個數組,用以收集攔截器函數,有fulfilledrejected,分別對應Promise的onSuccess和onFail的回調,接下來看看攔截器和發起http請求是如何結合在一塊兒的,咱們看看Axios的原型上的request方法:

Axios.prototype.request = function request(config) {
  /*eslint no-param-reassign:0*/
  // Allow for axios('example/url'[, config]) a la fetch API
  if (typeof config === 'string') {
    config = arguments[1] || {};
    config.url = arguments[0];
  } else {
    config = config || {};
  }

  config = mergeConfig(this.defaults, config);
  config.method = config.method ? config.method.toLowerCase() : 'get';

  // Hook up interceptors middleware
  var chain = [dispatchRequest, undefined];
  var promise = Promise.resolve(config);

  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });

  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });

  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
};
複製代碼

從上面能夠看出,它們結合的方式是使用Promise把攔截器和發起http請求的操做結合起來的,interceptors.request會安排在發起http請求的操做前,interceptors.response會安排在發起http請求的操做後。

上傳和下載的進度

axios提供了觀察上傳和下載進度的功能,不過僅支持在瀏覽器環境中,核心代碼以下:

// Handle progress if needed
if (typeof config.onDownloadProgress === 'function') {
  request.addEventListener('progress', config.onDownloadProgress);
}

// Not all browsers support upload events
if (typeof config.onUploadProgress === 'function' && request.upload) {
  request.upload.addEventListener('progress', config.onUploadProgress);
}
複製代碼

從上面能夠看出,下載進度回調其實就是監聽XMLHTTPRequest對象的progress事件,上傳進度回調其實就是XMLHTTPRequest對象的upload屬性的progress事件

模擬響應數據

官方文檔裏指出這個功能須要開發者返回一個Promise對象而且在Promise裏返回一個有效的Response對象:

// `adapter` allows custom handling of requests which makes testing easier.
// Return a promise and supply a valid response (see lib/adapters/README.md).
adapter: function (config) {
  /* ... */
}
複製代碼

咱們能夠在源碼中找到這個功能的實現方式:

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

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

  // Transform response data
  response.data = transformData(
    response.data,
    response.headers,
    config.transformResponse
  );

  return response;
}, function onAdapterRejection(reason) {
  if (!isCancel(reason)) {
    throwIfCancellationRequested(config);

    // Transform response data
    if (reason && reason.response) {
      reason.response.data = transformData(
        reason.response.data,
        reason.response.headers,
        config.transformResponse
      );
    }
  }

  return Promise.reject(reason);
});
複製代碼

從上面能夠看出,若是咱們在使用axios發出http請求時,若是傳入的config對象有adapter屬性,這個屬性會頂替了默認的adapter(NodeJS的http.request()或XMLHTTPRequest),因此咱們須要在config的adapter屬性中返回一個Promise,而且這個Promise會返回一個有效的Response對象。

自定義引發報錯的響應碼範圍

axios提供了一個功能,能夠自定義報錯的響應碼的範圍,能夠經過config.validateStatus來配置。

默認的範圍是200到300之間:

validateStatus: function validateStatus(status) {
  return status >= 200 && status < 300;
}
複製代碼

而在源碼中,這個方法是經過lib\core\settle.js來調用的:

module.exports = function settle(resolve, reject, response) {
  var validateStatus = response.config.validateStatus;
  if (!validateStatus || validateStatus(response.status)) {
    resolve(response);
  } else {
    reject(createError(
      'Request failed with status code ' + response.status,
      response.config,
      null,
      response.request,
      response
    ));
  }
};
複製代碼

從上面能夠看出,settle的入參很像Promise的resolve和reject,接下來,咱們看看settle又是在哪裏被調用的。

果不其然,在lib\adapters\http.jslib\adapters\xhr.js中都看到settle的身影。

細節就不說了,我大體說一下思路,就是axios使用Promise發起http請求後,會把傳入Promise對象的函數中的resolve和reject再次傳遞給settle中,讓它來決定Promise的狀態是onResolved仍是onRejected。

取消請求的功能

axios官方文檔指出axios提供了取消已經發出去的請求的功能。

The axios cancel token API is based on the withdrawn cancelable promises proposal.

上面引用的話裏指出這是一個promise的提議,不過已經被撤回了。

在這裏,筆者想說的是,其實不依賴這個提議,咱們也能夠寫一個簡單取消請求的功能,只要你熟悉閉包就能夠了。

思路是這樣的:咱們可使用閉包的方式維護一個是否取消請求的狀態,而後在處理Promise的onResolved回調的時候判斷一下這個狀態,若是狀態是須要取消請求的話,就reject結果,大體以下:

function dispatchRequest(config) {
  let hasCancled = false;
  return Promise((resolve, reject) => {
    if (hasCancled) {
      reject({ hasCancled: true })
    } else {
      /** 處理正常響應 **/
    }
  })
    .then(/** 其餘業務操做 **/)
    .catch(err => {
      if (err.hasCancled) {
        /** 處理請求被取消 */
      }
    })
}
複製代碼

總結

最後,咱們能夠大體瞭解了axios強大的背後緣由:使用適配器模式屏蔽了平臺差別性,並提供統一的API,使用Promise的鏈式調用來保證整個請求過程的有序性和加強一些額外的功能。

axios庫是一個很精美的第三庫,值得咱們去讀讀它的源碼。你也會收穫不少的。很感謝你能堅持看到這裏。

不只文章裏提到的,還有好幾個有趣的課題值得大夥去研究,好比:

  1. axios是如何設置請求超時的
  2. axios是如何實現代理的
相關文章
相關標籤/搜索