Axios

Promise based HTTP client for the browser and Node.js – Promise based HTTP library that can be used in browsers and NodeJs

Characteristics (Ability)

  • XMLHttpRequests can be created from the browser
  • Support for creating HTTP requests from NodeJs
  • Supporting Promise API
  • Intercept requests and responses
  • Transform request data and response data, automatically transform JSON data
  • Cancel the request
  • The client supports XSRF defense

Axios is so widely used today that front-end developers often use it to make AJAX requests and to do front-end and back-end data interactions. So this article focuses on the axiOS implementation logic on the browser side, including

  • Implementation of multiple invocation methods
  • Implementation of multiple pass configurations
  • Implementation of interceptors
  • The implementation of converting request data and response data and automatically converting JSON data
  • Cancel the implementation of the request

The directory structure

How to implement multiple invocation methods

If you use Axios a lot, you should know how to use axios. For example:

import axios from 'axios';

axios(config) // Pass in the configuration directly
axios(url[, config]) // Pass in the URL and configuration
axios[method](url[, option]) // Call the request method directly, passing in the URL and configuration
axios[method](url[, data[, option]]) // Call the request method directly, passing in data, URL, and configuration
axios.request(option) // Call the request method

const axiosInstance = axios.create(config)
// axiosInstance also has the above axios capabilities

axios.all([axiosInstance1, axiosInstance2]).then(axios.spread(response1, response2))
// Call all and pass the spread callback
Copy the code

For many of these uses, the internals of AXIos are actually represented in axios.js, a file that exposes the interface

function createInstance(defaultConfig) {
  var context = new Axios(defaultConfig);

  // Instance refers to the Request method, and the context refers to the context, so it can be called directly with instance(option)
  // axios.prototype. request specifies the datatype of the first parameter, which allows us to call instance(URL, option)
  var instance = bind(Axios.prototype.request, context);

  // Copy axios.prototype to instance
  // Extend the axios.prototype method to instance,
  // Specify the context as context so that when the method on the Axios prototype chain is executed, this points to the context
  utils.extend(instance, Axios.prototype, context);

  // Copy context to instance
  // Extend the context object's own properties and methods to instance
  // Note: Because the forEach method that extends internally does for in traversal of the object, it traverses only properties of the object itself, not properties on the prototype chain
  // In this case, instance has the defaults and interceptors attributes.
  utils.extend(instance, context);
  return instance;
}

// Create the default instance to be exported creates an AXIOS instance generated by the default configuration
var axios = createInstance(defaults);

// Factory for creating new Instances extends the axios.create Factory function, which is also createInstance inside
axios.create = function create(instanceConfig) {
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};

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

axios.spread = function spread(callback) {
  return function wrap(arr) {
    return callback.apply(null, arr);
  };
};

module.exports = axios;
Copy the code

The main core is axios.prototype.request. All kinds of request invocation are realized inside request. Let’s take a look at the logic of request

Axios.prototype.request = function request(config) {
  // Allow for axios('example/url'[, config]) a la fetch API
  // Determine if the config argument is a string. If so, the first argument is a URL and the second argument is a real config
  if (typeof config === 'string') {
    config = arguments[1) | | {};// Place the URL in the config object for later mergeConfig
    config.url = arguments[0];
  } else {
    // If the config argument is not a string, the whole thing is treated as config
    config = config || {};
  }
  // Merge the default configuration with the incoming configuration
  config = mergeConfig(this.defaults, config);
  // Set the request method
  config.method = config.method ? config.method.toLowerCase() : 'get';
  /* something... This section will be covered separately in future interceptors
};

// Mount request methods 'delete', 'get', 'head', 'options' with no arguments on the Axios prototype
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
    }));
  };
});

// Mount the request methods' POST ', 'put', 'patch' and pass parameters on the Axios prototype, which is also request inside the implementation
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
    }));
  };
});
Copy the code

Start with the config configuration parameters

The config configuration should run through the “life” of Axios, from createInstance in the entry axios.js to the actual request method axios.prototype.request, so we’ll just go with it for now

Config in Axios is mainly distributed in these places:

  • The default configurationdefaults.js
  • The config. Method by defaultget
  • callcreateInstanceMethod to create an axios instance, passed in config
  • Directly or indirectly calledrequestMethod, passed in config

The priority relationships between these four areas are as follows:

Embodied in the source code:

// axios.js
// Create an axiOS instance generated by the default configuration
var axios = createInstance(defaults);

// Extend axios.create factory function, also createInstance inside
axios.create = function create(instanceConfig) {
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};

// Axios.js
// Merge the default configuration with the incoming configuration
config = mergeConfig(this.defaults, config);
// Set the request method
config.method = config.method ? config.method.toLowerCase() : 'get';
Copy the code

A request for a real request

The axios.prototype. request method, one of axios’s core methods, was introduced in the axios usage method and config flow configuration section

Axios.prototype.request = function request(config) {
  /* mergeConfig... Etc., no longer elaborate */
  // Hook up Interceptors Middleware creates a chain of interceptors
  var chain = [dispatchRequest, undefined];

  // This is a big pity. // This is a big pity. // This is a big pity
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    Note that forEach here is the forEach method of the custom interceptor
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });

  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    Note that forEach here is the forEach method of the custom interceptor
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });

  // Initialize a Promise object in the resolved state and receive the config object that has been processed and merged
  var promise = Promise.resolve(config);

  // Loop the chain of interceptors
  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift()); // Eject interceptor every time
  }
  / / return promise
  return promise;
};
Copy the code

Maybe this code looks a little confusing because it uses promises, interceptors. So let’s start with promise, which is a JavaScript solution to the purgatory of asynchronous callbacks. Then there are interceptors, which are the ———— interceptors for intercepting requests and responses mentioned earlier in this article.

The interceptor

The purpose of the interceptor is to reprocess the requested config before we make the request and intercept the requested result before we get the result. The official timing is then or before catch.

This.interceptors is an attribute on the axios instance. It is initialized in axios.js and has two attributes: request and response.

function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(), // Request interception
    response: new InterceptorManager() // Response interception
  };
}
Copy the code

It’s defined in interceptorManager.js, and they both have a use method. The use method takes two arguments, the first of which is similar to Promise’s resolve function and the second to Promise’s Reject function. We can execute synchronous or asynchronous code logic in resolve and reject.

// The initialization of an interceptor is essentially a set of hook functions
function InterceptorManager() {
  this.handlers = [];
}

// Calling the use of an interceptor instance pushes the method into the hook function
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected
  });
  return this.handlers.length - 1;
};

// Interceptor methods can be nulled based on the ID returned by the use method
// The reason why we can't use splice or slice is that the id will change after deleting, resulting in uncontrollable order or operation
InterceptorManager.prototype.eject = function eject(id) {
  if (this.handlers[id]) {
    this.handlers[id] = null; }};// This is where the loop interceptor's method forEach loop executes the hook function in the Axios request method
InterceptorManager.prototype.forEach = function forEach(fn) {
  utils.forEach(this.handlers, function forEachHandler(h) {
    if(h ! = =null) { fn(h); }}); };Copy the code

Now that you know what an interceptor is, it’s time to go back to Request and see how it works. Previously for interceptors, we knew that the request interceptor method was unshift into the interceptor and the response interceptor was push into the interceptor. Eventually they will concatenate a method called dispatchRequest to be executed sequentially by subsequent promises.

The result of the splicing is a chain like this:

Request interceptor2Resolve, request interceptor2Reject, request interceptor1Resolve, request interceptor1Reject, dispatchRequest,undefined, response interceptor1Resolve, the response interceptor1Reject, which responds to interceptors2Resolve, the response interceptor2Reject,]Copy the code

So interceptors are executed in chain order. In the case of request interceptors, later-added interceptors are executed during the pre-request process; For response interceptors, the interceptor added first is executed after the response.

After constructing the PromiseChain, we see the interceptor handlers on both sides and a dispatchRequest in the middle, which is where Axios actually initiates a request, as we’ll see in more detail.

Then define a promise that has resolved config, loop through the chain, take each interceptor object, and add their Resolved and Rejected functions to the promise.then argument. This is equivalent to the chain invocation of a Promise, which ultimately returns a Promise, implementing the effect of layer upon layer of chain invocation of the interceptor.

This concludes the first half of the section and the analysis of dispatchRequest and ancillary functions such as data conversion and cancellation request will follow. Axios source code