What is Axios?

Axios is a Promise-based HTTP library that can be used in browsers and Node.js. Today we are going to go into Axios source code parsing

Axios function

  • Create XMLHttpRequests from the browser
  • Create HTTP requests from Node.js
  • Supporting Promise API
  • Intercept requests and responses
  • Transform request data and response data
  • Cancel the request
  • Automatically convert JSON data
  • The client supports XSRF defense

Hope to clear the realization principle of these functions slowly through the source code

Axios use

Performing a GET request

axios.get('/user? ID=12345')
  .then(function (response) {
    console.log(response);
  })
Copy the code

Performing a POST request

axios.post('/user', {
    name: 'zxm'.age: 18,
  })
  .then(function (response) {
    console.log(response);
  })
Copy the code

Usage is not the focus of this topic, please refer to Axios Chinese instructions for specific usage

Pull down the source code and go directly to the lib folder to start reading the source code

The source code interpretation

Lib/axios. Js began

'use strict';

var utils = require('./utils');
var bind = require('./helpers/bind');
var Axios = require('./core/Axios');
var mergeConfig = require('./core/mergeConfig');
var defaults = require('./defaults');

// Focus on createInstance
CreateInstance = createInstance; createInstance = createInstance
function createInstance(defaultConfig) {
    // instantiate Axios
  var context = new Axios(defaultConfig);
    / / custom bind () method returns a function = > {Axios. Prototype. Request. Apply (context, args)}
  var instance = bind(Axios.prototype.request, context);
    // Axios source utility class
  utils.extend(instance, Axios.prototype, context);
    
  utils.extend(instance, context);
    
  return instance;
}
// Pass in the default defaults configuration
var axios = createInstance(defaults);


// Add different methods to axios instantiated objects.
axios.Axios = Axios;
axios.create = function create(instanceConfig) {
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');
axios.all = function all(promises) {
  return Promise.all(promises);
};
axios.spread = require('./helpers/spread');
module.exports = axios;
module.exports.default = axios;
Copy the code

Lib/util.js tool methods

There are the following methods:

module.exports = {
  isArray: isArray,
  isArrayBuffer: isArrayBuffer,
  isBuffer: isBuffer,
  isFormData: isFormData,
  isArrayBufferView: isArrayBufferView,
  isString: isString,
  isNumber: isNumber,
  isObject: isObject,
  isUndefined: isUndefined,
  isDate: isDate,
  isFile: isFile,
  isBlob: isBlob,
  isFunction: isFunction,
  isStream: isStream,
  isURLSearchParams: isURLSearchParams,
  isStandardBrowserEnv: isStandardBrowserEnv,
  forEach: forEach,
  merge: merge,
  deepMerge: deepMerge,
  extend: extend,
  trim: trim
};
Copy the code

The isXxx method name starting with is is used to determine whether the method is of type Xxx

Extend inherits properties and methods from B to A, and binds the execution of methods from B to thisArg

The a, b, thisArg arguments are all one object
function extend(a, b, thisArg) {
  forEach(b, function assignValue(val, key) {
      // Bind execution context to thisArg if thisArg is specified
    if (thisArg && typeof val === 'function') {
      a[key] = bind(val, thisArg);
    } else{ a[key] = val; }});return a;
}
Copy the code

Let’s do an abstract example

So you can see at a glance. Instead of taking age = 20 in its own object, fn2 is assigned age in thisArg

Custom forEach method traverses basic data, arrays, and objects.

function forEach(obj, fn) {
  if (obj === null || typeof obj === 'undefined') {
    return;
  }
  if (typeofobj ! = ='object') {
    obj = [obj];
  }
  if (isArray(obj)) {
    for (var i = 0, l = obj.length; i < l; i++) {
      fn.call(null, obj[i], i, obj); }}else {
    for (var key in obj) {
      if (Object.prototype.hasOwnProperty.call(obj, key)) {
        fn.call(null, obj[key], key, obj); }}}}Copy the code

Merge Merge attributes of an object, followed by the same attribute before the replacement

function merge(/* obj1, obj2, obj3, ... * /) {
  var result = {};
  function assignValue(val, key) {
    if (typeof result[key] === 'object' && typeof val === 'object') {
      result[key] = merge(result[key], val);
    } else{ result[key] = val; }}for (var i = 0, l = arguments.length; i < l; i++) {
    forEach(arguments[i], assignValue);
  }
  return result;
}
Copy the code

As shown below:

Bind -> lib/ helpers/ bind.js returns a function and the method execution context is bound to thisArg.

module.exports = function bind(fn, thisArg) {
  return function wrap() {
    var args = new Array(arguments.length);
    for (var i = 0; i < args.length; i++) {
      args[i] = arguments[i];
    }
    return fn.apply(thisArg, args);
  };
};
Copy the code

Ok, so the axios/util method we basically have no problem pulling

After looking at these utility class methods, let’s go back to the createInstance method

function createInstance(defaultConfig) {
    // instantiate Axios, which is covered below
  var context = new Axios(defaultConfig);
    
    // Bind the execution context of axios.prototype. request to context
    // Bind returns a function
  var instance = bind(Axios.prototype.request, context);
    
    // Bind the execution context of all methods on axios.prototype to context and inherit to instance
  utils.extend(instance, Axios.prototype, context);
    
    // Inherit context to instance
  utils.extend(instance, context);
    
  return instance;
}
// Pass in a default configuration
var axios = createInstance(defaults);
Copy the code

Conclusion: createInstance returns a function instance.

  1. Instance is a function axios.prototype. request and the execution context is bound to the context.
  2. Instance also contains all the methods above axios.prototype, and the execution context for those methods is bound to the context.
  3. Instance also has methods on context.

Axios instance source code

'use strict';
var utils = require('. /.. /utils');
var buildURL = require('.. /helpers/buildURL');
var InterceptorManager = require('./InterceptorManager');
var dispatchRequest = require('./dispatchRequest');
var mergeConfig = require('./mergeConfig');

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

// The core method request
Axios.prototype.request = function request(config) {
  / /... Speak alone
};

// Merge configuration Merge the user's configuration with the default configuration
Axios.prototype.getUri = function getUri(config) {
  config = mergeConfig(this.defaults, config);
  return buildURL(config.url, config.params, config.paramsSerializer).replace(/ ^ \? /.' ');
};
// Add the delete,get,head,options methods to Axios. Prototype
// Then we can use axios.get(), axios.post(), etc
utils.forEach(['delete'.'get'.'head'.'options'].function forEachMethodNoData(method) {
  Axios.prototype[method] = function(url, config) {
     // The this.request method is called
    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
    }));
  };
});

module.exports = Axios;

Copy the code

All of the above methods are done by calling this. Request

So let’s take a look at this request method, which I think is the essence of the source code and also the difficult part, using the chain call of Promise, but also using the idea of middleware.

function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}
Axios.prototype.request = function request(config) {
  // Allow for axios('example/url'[, config]) a la fetch API
  if (typeof config === 'string') {
    config = arguments[1) | | {}; config.url =arguments[0];
  } else {
    config = config || {};
  }
    // Merge the configuration
  config = mergeConfig(this.defaults, config);
    // Request mode, no default get
  config.method = config.method ? config.method.toLowerCase() : 'get';
    
    // This is the interceptor middleware
  var chain = [dispatchRequest, undefined];
    // Generate a Promise object
  var promise = Promise.resolve(config);

    // Place two successful and failed methods in front of the chain array
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });
	// Place the requested method into the end of the chain array
  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });

   Shift removes the first element and returns the first element.
  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
};
Copy the code

It’s a little abstract here, but that’s okay. Let’s start with interceptors. Intercepts requests or responses before they are processed by THEN or catch. Please refer to Axios Chinese instructions for usage as follows.

// Add request interceptor
axios.interceptors.request.use(function (config) {
    // What to do before sending the request
    return config;
  }, function (error) {
    // What to do about the request error
    return Promise.reject(error);
  });

// Add a response interceptor
axios.interceptors.response.use(function (response) {
    // What to do with the response data
    return response;
  }, function (error) {
    // Do something about the response error
    return Promise.reject(error);
  });
Copy the code

Use the promise chain to call each function, which is the method in the chain array

// The initial chain dispatchRequest array is a method for sending requests
var chain = [dispatchRequest, undefined];

// Then iterate through the interceptors
// Note that forEach is not array. forEach or util. ForEach. The specific interceptor source code will be covered
// Add two methods to the chain
  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);
  });

Copy the code

And then what happens if you add a request interceptor and a chain interceptor (important)

Chain = [success method of request interceptor, failure method of request interceptor, dispatchRequest, undefined, success method of response interceptor, failure method of response interceptor].Copy the code

Okay, so what does it look like when you use it? Go back to the request method

We have one at every request

 while(chain.length) { promise = promise.then(chain.shift(), chain.shift()); } means to take the methods in chainn in pairs and execute them as follows: promise.then(successful method for requesting interceptor, failed method for requesting interceptor). Then (dispatchRequest,undefined).then(method that responds to interceptor success, method that responds to interceptor failure)Copy the code

Now you can see that it’s a lot clearer how interceptors work. Now let’s look at the source code for InterceptorManager

InterceptorManager Interceptor source code

lib/ core/ InterceptorManager.js

'use strict';
var utils = require('. /.. /utils');

function InterceptorManager() {
    // An array of methods
  this.handlers = [];
}
// Add intercepting methods through the use method
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected
  });
  return this.handlers.length - 1;
};
// Use the eject method to remove intercepting methods
InterceptorManager.prototype.eject = function eject(id) {
  if (this.handlers[id]) {
    this.handlers[id] = null; }};// Add a forEach method
InterceptorManager.prototype.forEach = function forEach(fn) {
    // forEach is still used in utils
    // What did they do
  utils.forEach(this.handlers, function forEachHandler(h) {
    if(h ! = =null) { fn(h); }}); };module.exports = InterceptorManager;

Copy the code

DispatchRequest source

lib/ core/ dispatchRequest .js

'use strict';
var utils = require('. /.. /utils');
var transformData = require('./transformData');
var isCancel = require('.. /cancel/isCancel');
var defaults = require('.. /defaults');
var isAbsoluteURL = require('. /.. /helpers/isAbsoluteURL');
var combineURLs = require('. /.. /helpers/combineURLs');
// Request cancelling method, temporarily do not look
function throwIfCancellationRequested(config) {
  if(config.cancelToken) { config.cancelToken.throwIfRequested(); }}module.exports = function dispatchRequest(config) {
  throwIfCancellationRequested(config);
    // The request does not cancel execution of the request below
  if(config.baseURL && ! isAbsoluteURL(config.url)) { config.url = combineURLs(config.baseURL, config.url); } config.headers = config.headers || {};// Convert data
  config.data = transformData(
    config.data,
    config.headers,
    config.transformRequest
  );
    // Merge the configuration
  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) {
      deleteconfig.headers[method]; });// This is the main point. The way to get the request is described below
  var adapter = config.adapter || defaults.adapter;
  return adapter(config).then(function onAdapterResolution(response) {
    throwIfCancellationRequested(config);
	// Convert the requested data to data
    response.data = transformData(
      response.data,
      response.headers,
      config.transformResponse
    );
    return response;
  }, function onAdapterRejection(reason) {
      // Failed processing
    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);
  });
};

Copy the code

Now that we haven’t seen what the request is sent through, what are the defaults we pass in when we instantiate the createInstance method in the first place

var axios = createInstance(defaults);

lib/ defaults.js

'use strict';

var utils = require('./utils');
var normalizeHeaderName = require('./helpers/normalizeHeaderName');

var DEFAULT_CONTENT_TYPE = {
  'Content-Type': 'application/x-www-form-urlencoded'
};

function setContentTypeIfUnset(headers, value) {
  if(! utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) {
    headers['Content-Type'] = value; }}// The getDefaultAdapter method is the way to get the request
function getDefaultAdapter() {
  var adapter;
  // Process is a global variable in the Node environment
  if (typeofprocess ! = ='undefined' && Object.prototype.toString.call(process) === '[object process]') {
    // If the node environment is so long through the Node HTTP request method
    adapter = require('./adapters/http');
  } else if (typeofXMLHttpRequest ! = ='undefined') {
   // If the browser has XMLHttpRequest, use XMLHttpRequest
    adapter = require('./adapters/xhr');
  }
  return adapter;
}

var defaults = {
    // Adapter is the requested method
  adapter: getDefaultAdapter(),
	// Below are some request headers, transform data, request, detail data
    // This is why we can get the requested data directly from an object. If we use Ajax, we can get a jSON string
    // The result is then processed each time through json.stringify (data).
  transformRequest: [function transformRequest(data, headers) {
    normalizeHeaderName(headers, 'Accept');
    normalizeHeaderName(headers, 'Content-Type');
    if (utils.isFormData(data) ||
      utils.isArrayBuffer(data) ||
      utils.isBuffer(data) ||
      utils.isStream(data) ||
      utils.isFile(data) ||
      utils.isBlob(data)
    ) {
      return data;
    }
    if (utils.isArrayBufferView(data)) {
      return data.buffer;
    }
    if (utils.isURLSearchParams(data)) {
      setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded; charset=utf-8');
      return data.toString();
    }
    if (utils.isObject(data)) {
      setContentTypeIfUnset(headers, 'application/json; charset=utf-8');
      return JSON.stringify(data);
    }
    returndata; }].transformResponse: [function transformResponse(data) {
    /*eslint no-param-reassign:0*/
    if (typeof data === 'string') {
      try {
        data = JSON.parse(data);
      } catch (e) { /* Ignore */}}returndata; }]./** * A timeout in milliseconds to abort a request. If set to 0 (default) a * timeout is not created. */
  timeout: 0.xsrfCookieName: 'XSRF-TOKEN'.xsrfHeaderName: 'X-XSRF-TOKEN'.maxContentLength: - 1.validateStatus: function validateStatus(status) {
    return status >= 200 && status < 300; }}; defaults.headers = {common: {
    'Accept': 'application/json, text/plain, */*'}}; 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);
});

module.exports = defaults;

Copy the code

conclusion

  1. A walk through the Axios source code can actually see and learn a lot.
  2. Axios also has some functionality: request cancellation, request timeout handling. I haven’t covered all of that here.
  3. Axios makes it possible for clients to defend against XSRF Django CSRF by adding toke and validation methods to the request

The last

If you still don’t understand, don’t worry, this is basically my expression, writing is not good enough. Because when WRITING an article, I have repeatedly deleted, rewrite, always feel that the expression is not clear enough. You can go to Github and pull down the code and look at it in comparison. This will make parsing the Axios source code clearer

git clone https://github.com/axios/axios.git

The whole article, if there are mistakes or not rigorous place, please be sure to give correction, thank you!

Reference:

  • Axios Chinese instructions, very detailed tutorial
  • Axios source making
  • Recommended Axios source code in-depth analysis