一篇文章帶你深度解析 axios 源碼

axios深度解析(源碼)

axios的作用

支持請求/響應攔截器

支持取消請求

請求/響應數據轉換

批量發送多個請求

axios基本使用

npm install axios 該文件 在axios目錄下的lib下的axios.js

博客的目的通過對源碼的解析來完成對axios的執行流程有一個深入的瞭解。

先說一下axios常用語法

  • axios(config):通用/最本質的發任意類型請求的方式
  • axios(ur1[, config]): 可以只指定ur1發get請求
  • axios.request(config):等同於axios(config)
  • axios.get(ur1[, config]): 發get請求
  • axios.delete(url[, config]): 發delete請 求
  • axios.post(ur1[, data, config]): 發post請求
  • axios.put(url[, data, config]): 發put請求
  • axios.defaults . xxx:請求的默認全局配置
  • axios.interceptors.request.use():添加請求攔截器
  • axios.interceptors.response.use():添加響應攔截器
  • axios.create( [config]):創建個 新的axios(它沒有 下面的功能)
  • axios.Cancel():用於創建取消請求的錯誤對象
  • axios.CancelToken():用於創建取消請求的token對象
  • axios.isCancel():是否是一個取消請求的錯誤
  • axios.all(promises):用於批量執行多個異步請求
  • axios.spread():用來指定接收所有成功數據的回調函數的方法

其實上面這一些用法有一個核心的代碼就是request機制,理解了源碼中的request機制會對axios有更全面的認知。

axios入口文件核心代碼

首先我們先npm install axios下載下來這個包,然後進入axios目錄下的lib中的axios.js文件來看下面的核心代碼。

axios入口文件

var utils = require('./utils'); //下面代碼中拷貝的模塊
var bind = require('./helpers/bind'); // bind模塊 修改this
var Axios = require('./core/Axios'); //Axios核心模塊
var mergeConfig = require('./core/mergeConfig'); // 合併模塊
var defaults = require('./defaults'); //默認配置模塊 
//這個defaults默認有很多屬性,這些屬性就是在defaults.js中可以找到

首先 它引入 了許多東西,分別是一些功能函數和一些配置,這裏重點關注一下Axios和defaults模塊。

// axios和axios.create()直接調用這個函數
function createInstance(defaultConfig) {
  //創建一個Axios的實例,傳入這個配置對象,這個Axios由於是Axios模塊中,則會在下面代碼中展示出來並分析。
  var context = new Axios(defaultConfig);
    
  //等同於Axios.prototype.request.bind(context) 
  //把this指定成Axios的實例就能使用Axios上的屬性了,使內部使用的this正確。
  var instance = bind(Axios.prototype.request, context);

  // Copy axios.prototype to instance   
  //擴展 將第二個Axios原型上的所有東西都拷貝到instance上 如: Axios.prototype.geturi ,Axios.prototype.get 等方法
  utils.extend(instance, Axios.prototype, context);

  // Copy context to instance 又把實例上的所有東西copy到instance上, 實例上有defaults和interceptors,代碼在下面Axios核心模塊中
  utils.extend(instance, context);
 
  return instance;
  //經過這一大通的拷貝 他的作用是什麼呢? 爲什麼不直接使用實例返回呢? 因爲這樣的話他就不能axios()直接這樣加括號執行了。
  // 所以這個函數的返回值本質上就是 Axios.prototype.request, 這個會在下面詳細講解這個request
}

//我們的axios直接調用的是這個createInstance函數
var axios = createInstance(defaults);

// Expose Axios class to allow class inheritance
axios.Axios = Axios;
// Factory for creating new instances
axios.create = function create(instanceConfig) {
  //這個傳入的參數 mergeConfig 拿着axios默認配置和 一個新的配置(instanceConfig)合併成一個新的配置
  //這個就是爲什麼有多個後臺接口就可以使用 這個方法 他這個配置是獨立的
  //和axios的區別 create這個沒有後面添加的cancel/cancelToken等
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};
// axios和axios.create的功能基本一致 由此可見調用axios和使用create方法創建本質上都是調用的createInstance方法
// Expose Cancel & CancelToken 下面這些屬性都是 instance 沒有的。
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');

// Expose all/spread
axios.all = function all(promises) {
  return Promise.all(promises);
};
axios.spread = require('./helpers/spread');

module.exports = axios;

// Allow use of default import syntax in TypeScript
module.exports.default = axios;

Axios核心模塊

Axios構造函數

//這裏只寫出了 Axios構造函數 詳細內容在 下面request中
function Axios(instanceConfig) {
  //把配置對象添加到 這個defaults屬性上
  this.defaults = instanceConfig;
  //將包含請求/響應攔截器 的對象保存到interceptors上
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}
//中間我省略了中間代碼,由於這個函數尤其重要 我放到下面interceptors中單獨講解。
Axios.prototype.request = function request(config){
    //此間省略大量代碼
}



Axios.prototype.getUri = function getUri(config) {
  config = mergeConfig(this.defaults, config);
  return buildURL(config.url, config.params, config.paramsSerializer).replace(/^\?/, '');
};

// 下面代碼把這個發請求的方法都綁定到Axios.prototype上
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url
    }));
  };
});

utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, data, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url,
      data: data
    }));
  };
});

defaults模塊:

如下部分代碼可以看出爲什麼defaults中爲什麼會有那麼多的默認配置。讓然還有很多屬性沒有列出,有興趣可以自己看一下。

utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) {
  defaults.headers[method] = {};
});

utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE);
});

request和 interceptors (攔截器)

Axios核心模塊

request整體流程 前面的話都是一些配置的處理,可以不用太關注

// axios()和axios.request()這兩種寫法是一樣的,這些方法目前還在Axios的原型上
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);

  // Set config.method
  if (config.method) {
    config.method = config.method.toLowerCase();
  } else if (this.defaults.method) {
    config.method = this.defaults.method.toLowerCase();
  } else {
    config.method = 'get';
  }

  // Hook up interceptors middleware  下面是重點 (圈起來 要考!)
  // 有一個數組數組的左邊放請求攔截器,數組的右邊放響應攔截器,中間這個dispatchRequest是用來發請求的函數,詳細分析如 圖一:
  var chain = [dispatchRequest, undefined];
  //成功的promise 值就是這個config
  var promise = Promise.resolve(config);
  //unshift
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });
  // push
  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });

  //  
  while (chain.length) {
      //重點 (圈起來 要考) 這一步的作用就是 在圖二中把 數組的第一項,和第二項(攔截器成功和失敗回調) 取出 ,作爲這個promise成功的回調和失敗的回調,然後循環遍歷這個數組,取出所有的請求攔截器
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
};

圖一:

在這裏插入圖片描述

有多個請求攔截器, 請求攔截器執行和 添加請求攔截器的順序是相反的,就比如你添加了兩個請求攔截器,後添加的那一個先執行,請求攔截器 執行完之後 ,就會發請求,然後會有響應結果,有了響應結果後 執行響應攔截器,執行完響應攔截器 然後 就是執行 成功和失敗的回調 .then()方法。

圖二:

在這裏插入圖片描述

能堅持到這裏的小夥伴 已經很不容易了 可能有小夥伴不太理解這個 dispatch Request 怎麼發的請求 ,同樣我們進入這個模塊–擼源碼。

dispatchRequest模塊和adapters

module.exports = function dispatchRequest(config) {
      // Transform request data 數據的轉換  json處理
    config.data = transformData(
         config.data,
         config.headers,
   		 config.transformRequest
    );
    //省略很多代碼
    var adapter = config.adapter || defaults.adapter; 
    
    return adapter(config).then(function onAdapterResolution(response) {
        //省略很多代碼
        //它返回的也是一個promise ,我們需要知道這個adapter 最終找的是誰?
    }
}

我們需要知道這個adapter 最終找的是誰?在lib下有一個爲adapters的目錄 其中有http.js和xhr.js.我們發的ajax用的就是這個xhr模塊 ,在這個模塊中 有一個xhrAdapter(config) 方法,返回的也是一個promise ,發請求的具體方法 就在這個函數內部,源碼就不貼了 (有興趣的小夥伴可以看一下)。

現在我總結一下這個流程 request(config) —> dispathRequest(config) —> xhrAdapter(config)

request(config) :|將請求攔截器/ dispatchRequest() /響應攔截器通過promise鏈串連起來,返回promise
dispatchRequest(config):轉換請求數據 —> 調用xhrAdapter()發請求 —>請求返回後轉換響應數據。返回promise
xhrAdapter(config):創建XHR對象,根據config進行相應設置,發送特定請求,並接收響應數據,返回promise

文章最後有這個流程圖

axios取消請求cancel

lib下有一個cancel目錄,裏面有三個文件分別爲 Cancel.js,CancelToken.js,isCancel.js

Cancel模塊

//構造函數 也是一個error
function Cancel(message) {
  this.message = message;
}

Cancel.prototype.toString = function toString() {
  return 'Cancel' + (this.message ? ': ' + this.message : '');
};

Cancel.prototype.__CANCEL__ = true;

module.exports = Cancel;

isCancel模塊

//判斷 是否是這個Cancel 還是一個一般的error
module.exports = function isCancel(value) {
  return !!(value && value.__CANCEL__);
};

CancelToken模塊

function CancelToken(executor) {
  if (typeof executor !== 'function') {
    throw new TypeError('executor must be a function.');
  }

  var resolvePromise;
  this.promise = new Promise(function promiseExecutor(resolve) {
    resolvePromise = resolve;
  });

  var token = this;
  executor(function cancel(message) {
    if (token.reason) {
      // Cancellation has already been requested
      return;
    }
    //Cancel模塊 
    token.reason = new Cancel(message);
    //將取消請求的promise 指定爲成功 值爲reason 
    resolvePromise(token.reason); 
    //之後就會執行xhr.js模塊的中的config.cancelToken.promise.then()
  });

xhr.js

//xhr中的 有關中斷請求的部分代碼
config.cancelToken.promise.then(function onCanceled(cancel) {
    if (!request) {
        return;
    }
	// 中斷
    request.abort();
    //promise進入失敗 傳入這個cancel對象 這個就是 error 
    reject(cancel);
    // Clean up request
    request = null;
});

整體流程圖

爲什麼把流程圖放最後而不是放前面,因爲這個圖 不瞭解源碼看了跟沒看一樣。
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章