axios源碼分析

項目主要技術棧是Vue,因此咱們在請求後端接口的時候通常會用到axios的組件,那麼咱們就來分析一下它的源碼及它的實現。javascript

axios主要目錄結構

├── /dist/                     # 項目輸出目錄
├── /lib/                      # 項目源碼目錄
│ ├── /cancel/                 # 定義取消功能
│ ├── /core/                   # 一些核心功能
│ │ ├── Axios.js               # axios的核心主類---------------------------這是其最核心部分
│ │ ├── dispatchRequest.js     # 用來調用http請求適配器方法發送請求         |
│ │ ├── InterceptorManager.js  # 攔截器構造函數                            |
│ │ └── settle.js              # 根據http響應狀態,改變Promise的狀態--------
│ ├── /helpers/                # 一些輔助方法
│ ├── /adapters/               # 定義請求的適配器 xhr、http----這個文件夾封裝了ajax的請求
│ │ ├── http.js                # 實現http適配器
│ │ └── xhr.js                 # 實現xhr適配器
│ ├── axios.js                 # 對外暴露接口
│ ├── defaults.js              # 默認配置 
│ └── utils.js                 # 公用工具
├── package.json               # 項目信息
├── index.d.ts                 # 配置TypeScript的聲明文件
└── index.js                   # 入口文件

由使用到探索

咱們通常使用的時候都會先new一個axios實例出來,那麼這個實例是怎麼來的呢?裏邊都有什麼呢?咱們來看一下源代碼最外層一個axios.js文件java

function createInstance(defaultConfig) {
  var context = new Axios(defaultConfig);
  var instance = bind(Axios.prototype.request, context);

  // Copy axios.prototype to instance
  utils.extend(instance, Axios.prototype, context);

  // Copy context to instance
  utils.extend(instance, context);

  return instance;
}

// Create the default instance to be exported
var axios = createInstance(defaults);

// Expose Axios class to allow class inheritance
axios.Axios = Axios;

// Factory for creating new instances
axios.create = function create(instanceConfig) {
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};

經過源代碼咱們能夠看到axios導出的axios對象中有一個create方法,還有個Axios對象,同時axios自己仍是createInstance函數,而且他們都會傳遞一個參數進去,因此咱們在使用axios的時候會有多種不一樣的調用方法,他們最終實例化的實際上是core文件下的Axios.js中所寫的Axios構造函數。ios

// /lib/core/Axios.js
function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}

Axios.prototype.request = function request(config) {
  // ...省略代碼
};

// 爲支持的請求方法提供別名
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  Axios.prototype[method] = function(url, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url
    }));
  };
});
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  Axios.prototype[method] = function(url, data, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url,
      data: data
    }));
  };
});

上邊是部分源代碼,咱們能夠看到在這個構造函數中他會有個default對象,是用來存放咱們的配置信息的,還有個interceptors對象,裏邊包了兩個實例,分別對應了request攔截器和response攔截器。而且在Axios的原型鏈上有寫了一個request方法,並對支持的請求方法都賦值了一個別名。ajax

咱們的配置config是如何生效的

咱們看源代碼會發現幾乎每一個方法都會有一個config參數,咱們使用的時候的config就是經過這種方式貫穿整個axios的。咱們來看一下咱們使用的3種方式:json

import axios from 'axios'

// 第1種:直接修改Axios實例上defaults屬性,主要用來設置通用配置
axios.defaults[configName] = value;

// 第2種:發起請求時最終會調用Axios.prototype.request方法,而後傳入配置項,主要用來設置「個例」配置
axios({
  url,
  method,
  headers,
});

// 第3種:新建一個Axios實例,傳入配置項,此處設置的是通用配置
let newAxiosInstance = axios.create({
  [configName]: value,
});

對比咱們剛纔所說的axios實例的生成,咱們能夠看到這三種方式其實都會把config用過參數的形式傳遞進去,而且在每一步調用的時候生效。axios

請求攔截器和響應攔截器

咱們在上邊看axios的構造函數的時候看到他綁定了一個request攔截器和response攔截器,那麼他們是作什麼的呢?又是如何生效的呢?顧名思義,攔截器就是攔截請求的,能夠在發送request以前或者接收response以前進行攔截,而後作一些事情達到咱們的目的。後端

// /lib/core/InterceptorManager.js

function InterceptorManager() {
  this.handlers = []; // 存放攔截器方法,數組內每一項都是有兩個屬性的對象,兩個屬性分別對應成功和失敗後執行的函數。
}

// 往攔截器裏添加攔截方法
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected
  });
  return this.handlers.length - 1;
};

// 用來註銷指定的攔截器
InterceptorManager.prototype.eject = function eject(id) {
  if (this.handlers[id]) {
    this.handlers[id] = null;
  }
};

// 遍歷this.handlers,並將this.handlers裏的每一項做爲參數傳給fn執行
InterceptorManager.prototype.forEach = function forEach(fn) {
  utils.forEach(this.handlers, function forEachHandler(h) {
    if (h !== null) {
      fn(h);
    }
  });
};
// /lib/core/Axios.js
Axios.prototype.request = function request(config) {
  // ...
  var chain = [dispatchRequest, undefined];

  // 初始化一個promise對象,狀態微resolved,接收到的參數微config對象
  var promise = Promise.resolve(config);

  // 注意:interceptor.fulfilled 或 interceptor.rejected 是可能爲undefined
  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);
  });

  // 添加了攔截器後的chain數組大概會是這樣的:
  // [
  //   requestFulfilledFn, requestRejectedFn, ..., 
  //   dispatchRequest, undefined,
  //   responseFulfilledFn, responseRejectedFn, ....,
  // ]

  // 只要chain數組長度不爲0,就一直執行while循環
  while (chain.length) {
    // 數組的 shift() 方法用於把數組的第一個元素從其中刪除,並返回第一個元素的值。
    // 每次執行while循環,從chain數組裏按序取出兩項,並分別做爲promise.then方法的第一個和第二個參數

    // 按照咱們使用InterceptorManager.prototype.use添加攔截器的規則,正好每次添加的就是咱們經過InterceptorManager.prototype.use方法添加的成功和失敗回調

    // 經過InterceptorManager.prototype.use往攔截器數組裏添加攔截器時使用的數組的push方法,
    // 對於請求攔截器,從攔截器數組按序讀到後是經過unshift方法往chain數組數裏添加的,又經過shift方法從chain數組裏取出的,因此得出結論:對於請求攔截器,先添加的攔截器會後執行
    // 對於響應攔截器,從攔截器數組按序讀到後是經過push方法往chain數組裏添加的,又經過shift方法從chain數組裏取出的,因此得出結論:對於響應攔截器,添加的攔截器先執行

    // 第一個請求攔截器的fulfilled函數會接收到promise對象初始化時傳入的config對象,而請求攔截器又規定用戶寫的fulfilled函數必須返回一個config對象,因此經過promise實現鏈式調用時,每一個請求攔截器的fulfilled函數都會接收到一個config對象

    // 第一個響應攔截器的fulfilled函數會接受到dispatchRequest(也就是咱們的請求方法)請求到的數據(也就是response對象),而響應攔截器又規定用戶寫的fulfilled函數必須返回一個response對象,因此經過promise實現鏈式調用時,每一個響應攔截器的fulfilled函數都會接收到一個response對象

    // 任何一個攔截器的拋出的錯誤,都會被下一個攔截器的rejected函數收到,因此dispatchRequest拋出的錯誤纔會被響應攔截器接收到。

    // 由於axios是經過promise實現的鏈式調用,因此咱們能夠在攔截器裏進行異步操做,而攔截器的執行順序仍是會按照咱們上面說的順序執行,也就是 dispatchRequest 方法必定會等待全部的請求攔截器執行完後再開始執行,響應攔截器必定會等待 dispatchRequest 執行完後再開始執行。

    promise = promise.then(chain.shift(), chain.shift());

  }

  return promise;
};

dispatchrequest作了什麼事情

咱們再看攔截器生效的時候發如今Axios.prototype.request 中會有這麼一段代碼var chain = [dispatchRequest, undefined];,而且會在promise = promise.then(chain.shift(), chain.shift());被調用,那麼這個dispatchrequest是什麼呢?數組

// /lib/core/dispatchRequest.js
module.exports = function dispatchRequest(config) {
  throwIfCancellationRequested(config);

  // Support baseURL config
  if (config.baseURL && !isAbsoluteURL(config.url)) {
    config.url = combineURLs(config.baseURL, config.url);
  }

  // Ensure headers exist
  config.headers = config.headers || {};

  // 對請求data進行轉換
  config.data = transformData(
    config.data,
    config.headers,
    config.transformRequest
  );

  // 對header進行合併處理
  config.headers = utils.merge(
    config.headers.common || {},
    config.headers[config.method] || {},
    config.headers || {}
  );

  // 刪除header屬性裏無用的屬性
  utils.forEach(
    ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
    function cleanHeaderConfig(method) {
      delete config.headers[method];
    }
  );

  // http請求適配器會優先使用config上自定義的適配器,沒有配置時纔會使用默認的XHR或http適配器,不過大部分時候,axios提供的默認適配器是可以知足咱們的
  var adapter = config.adapter || defaults.adapter;

  return adapter(config).then(/**/);
};

經過源碼分析,咱們能夠看到dispatchrequest主要作了3件事情:promise

  • 拿到config對象,對config進行傳給http請求適配器前的最後處理;異步

  • http請求適配器根據config配置,發起請求 ;

  • http請求適配器請求完成後,若是成功則根據header、data、和config.transformResponse拿到數據,

轉換後的response,並return。

段落小結

經過上面對axios源碼結構分析,咱們能夠獲得axios搭建的一個主要結構:

調用===>Axios.prototype.request===>請求攔截器interceptors===>dispatchRequest===>請求轉換器transformRequest===>請求適配器xhrAdapter===>響應轉換器transformResponse===>響應攔截器interceptors

axios如何基於promise搭建異步橋樑

瞭解了axios的主要結構及調用順序,那咱們來看一下axios是如何經過promise來搭建異步的橋樑?

axios.get(/**/)
.then(data => {
  // 此處能夠拿到向服務端請求回的數據
});
.catch(error => {
  // 此處能夠拿到請求失敗或取消或其餘處理失敗的錯誤對象
});

咱們那get方法來舉例,當咱們調用axios的get方法的時候其實是調用axios原型鏈上的get方法,而其原型鏈上的get方法又指向了其原型鏈上的request方法。

utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  Axios.prototype[method] = function(url, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url
    }));
  };
});
Axios.prototype.request = function request(config) {
  // ...
  var chain = [dispatchRequest, undefined];
  // 將config對象看成參數傳給Primise.resolve方法
  var promise = Promise.resolve(config);

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

  return promise;
};

咱們能夠看到在request方法裏邊有個chain數組,這個實際上是至關於一個方法的隊列,這裏會把request的攔截器插入chain數組的前邊,response攔截器插入chain數組後邊,經過下邊的while循環,兩兩調用promise.then來順序執行請求攔截器到dispatchrequest再到響應攔截器的方法。

在dispatchrequest中呢又會調用封裝好的ajax(xhrAdapter方法),xhrAdapter方法返回的是還一個Promise對象

// /lib/adapters/xhr.js
function xhrAdapter(config) {
  return new Promise(function dispatchXhrRequest(resolve, reject) {
    // ... 省略代碼
  });
};

xhrAdapter內的XHR發送請求成功後會執行這個Promise對象的resolve方法,並將請求的數據傳出去, 反之則執行reject方法,並將錯誤信息做爲參數傳出去。

相關文章
相關標籤/搜索