Axios introduction

Axios is a Promise based HTTP request library that can be used in browsers and Node.js. It has been downloaded tens of millions of times a week on the NPM website and is popular with front-end developers.

Its usage, default configuration, and so on are not described here. You can check it out on the official website. Be sure to read the AXIos documentation carefully, as it is important to analyze the source code.

Axios directory structure and functions

The files listed below are in the lib directory, which is the source directory for AXIos. For other directories, go to GitHub and download the axios source.

├─ │ ├─ ├─ └ ├─ XHR. Js # Web browser └─ / Adapters / # Define Request adapter │ ├─ http.js # Web browser ├─ /cancel │ ├─ ├─ canceltoken.js # ├─ ├─ canceltoken.js # ├─ ├─ canceltoken.js # │ ├─ ├─ Axios. Js # Axios constructor Initialize the default configuration, request and response interceptors and request methods │ ├─ buildFullPath.js # Combine the base and request addresses into a new URL and return │ ├─ createError │ ├─ enhanceError. Js # Use the configured adapter to send a request to the server. │ ├─ enhanceError ├─ mergeConfig. Js # Merge configuration │ ├─ settling. Js # Change based on HTTP response statusPromiseState of resolve or reject │ └ ─ ─ transformData. Js # transform the request or response data ├ ─ ─ / helpers / # define helper methods function module │ ├ ─ ─ the bind. Js # change function execution context, namelythis│ ├─ Combineurls.js # Create a new URL by combining the specified URL │ ├─ combineurls.js # │ ├─ IsAbsoluteUr.js # ├─ isAbsoluteUr.js # │ ├─ IsurlsameOrigine.js # Return Boolean value │ ├─ isurlsameOrigine.js #true│ ├ ─ ─ normalizeHeaderName. Js # standardization request header attribute name │ ├ ─ ─ parseHeaders. Js # will request Headers head into an object │ └ ─ ─ spread. Js # used to invoke the function and the parameter array of syntactic sugar ├─ ├─ default.js # Public Tools ├─ default.js # Public ToolsCopy the code

Axios roughly executes the process

Axios source code analysis

Utility methods

Axios has many tools and methods, but here are a few that are important in interpreting the source code to help you quickly understand it.

  1. Bind alters the context in which the function is executed
// file location: lib/helpers/bind.js

module.exports = function bind(fn, thisArg) {
  // return a function wrap
  return function wrap() {
    // Create array args according to arguments.length with arguments in arguments
    // Store everything in this array
    var args = new Array(arguments.length);
    for (var i = 0; i < args.length; i++) {
      args[i] = arguments[i];
    }
    ThisArg as fn's execution context
    return fn.apply(thisArg, args);
  };
};
Copy the code
  1. TransformData Transforms request or response data
var utils = require('. /.. /utils');

/** * Convert the request or response data **@param {Object|String} Data Indicates the data to be converted@param {Array} Headers Headers of a request or response *@param {Array|Function} Fnsxz0985 -- array or Function, e.g. * FNS -- > [Function] or Function *@returns {*} Converted result data */
module.exports = function transformData(data, headers, fns) {
  utils.forEach(fns, function transform(fn) {
    data = fn(data, headers);
  });

  return data;
};
Copy the code
  1. Utils.js public utility partial methods
Helpers/lib/helpers/utils.js

/** * extends object A ** by variably adding object B's attributes to object A@param {Object} A The object to extend *@param {Object} B The object whose properties are to be copied *@param {Object} ThisArg to bind function object *@return {Object} The result value of object A is */
function extend(a, b, thisArg) {
  forEach(b, function assignValue(val, key) {
    if (thisArg && typeof val === 'function') {
      a[key] = bind(val, thisArg);
    } else{ a[key] = val; }});return a;
}

/** * iterates through arrays or objects, calling functions for each item. * If 'obj' is an array, the callback function is called, passing the value, index, and full array for each item *. * If 'obj' is an object, the callback function is called, passing the value, key, and full object for each attribute *. * *@param {Object|Array} Obj iterates object *@param {Function} Fn Calls the function */ for each item
function forEach(obj, fn) {
  if (obj === null || typeof obj === 'undefined') {
    return;
  }
  // Force an array if there are no iterable already
  if (typeofobj ! = ='object') {
    obj = [obj];
  }

  if (isArray(obj)) {
    // Iterate over the array values
    for (var i = 0, l = obj.length; i < l; i++) {
      fn.call(null, obj[i], i, obj); }}else {
    // Iterate over the key of the object
    for (var key in obj) {
      if (Object.prototype.hasOwnProperty.call(obj, key)) {
        fn.call(null, obj[key], key, obj); }}}}/** * takes mutable arguments, expects each argument to be an object, and then immutably merges the attributes of each object * and returns the result. * When multiple objects contain the same key, the object later in the argument list takes precedence, i.e. the latter merges the former. Var result = merge({foo: 123}, {foo: 456}); * console.log(result.foo); // Output: 456 * *@param {Object} Obj1 Object to merge *@returns {Object} The merged object */
function merge( /* obj1, obj2, obj3, ... * / ) {
  var result = {};
  function assignValue(val, key) {
    if (isPlainObject(result[key]) && isPlainObject(val)) {
       // Determine if a value is a normal Object, not an Array
      result[key] = merge(result[key], val);
    } else if (isPlainObject(val)) {
      result[key] = merge({}, val);
    } else if (isArray(val)) {
      // Determine if a value is Array
      result[key] = val.slice();
    } else{ result[key] = val; }}for (var i = 0, l = arguments.length; i < l; i++) {
    forEach(arguments[i], assignValue);
  }
  return result;
}
Copy the code

The default entry

Axios.js is the entry file to Axios. Its main functions are: initialize the default instance, provide the method to create a custom instance, cancel request method and concurrent request method. Here is the source code and comments.

// File location: lib/axios.js

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

/** * Create an Axios instance *@param {Object} DefaultConfig Default configuration *@return {Axios}  * / Axios instance

function createInstance(defaultConfig) {

  // Create an Axios instance and return it. At the same time, initialize the default configuration, request interceptors and response interceptors, and request methods.
  var context = new Axios(defaultConfig);

  // Use context as the execution context of axios.prototype. request and return a function.
  // The purpose is that we can use axios(...) This type of request.
  var instance = bind(Axios.prototype.request, context);

  // Extend the methods on the axios.prototype prototype to instance and specify Contex as the execution context.
  // The purpose is to provide aliases for all supported request methods so that we can use axios.get(...) such
  // Request mode.
  utils.extend(instance, Axios.prototype, context);

  // Extend properties and methods on context to instance
  utils.extend(instance, context);

  return instance;
}

// Create a default instance to export
var axios = createInstance(defaults);

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

// Create a custom instance
axios.create = function create(instanceConfig) {
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};

// 'Cancel' is an object thrown when the operation is cancelled
axios.Cancel = require('./cancel/Cancel');

// Create is an object that can be used to request cancellation
axios.CancelToken = require('./cancel/CancelToken');

// Determine if it is a cancellation request
axios.isCancel = require('./cancel/isCancel');

// Implement axiOS concurrent requests based on promise.all
axios.all = function all(promises) {
  return Promise.all(promises);
};
// Call the function and expand the syntax sugar for the array arguments. Use with axios.all.
axios.spread = require('./helpers/spread');

// Whether an error was thrown by Axios
axios.isAxiosError = require('./helpers/isAxiosError');

/ / exposed axios
module.exports = axios;

// Allows TypeScript to use the default import syntax
module.exports.default = axios;
Copy the code

Initialize the default instance

As you can see from the above code and comments, the createInstance function is called to create the instance. New Axios(defaultConfig) is the key to creating the instance.

// File location: lib/core/ axios.js

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


/** * Create an Axios instance *@param {Object} The default configuration for instanceConfig instances is */
function Axios(instanceConfig) {
  this.defaults = instanceConfig; 
  this.interceptors = {
    request: new InterceptorManager(), // Request interceptor
    response: new InterceptorManager() // Response interceptor
  };
}

/** * Send a request *@param {Object} Config Configuration for this request (merged with this.defaults) */
Axios.prototype.request = function request(config) {
  /*eslint no-param-reassign:0*/
  // If config === 'string', e.g. Axios (url[, config]) is used,
  // Turn it into an object
  if (typeof config === 'string') {
    config = arguments[1) | | {}; config.url =arguments[0];
  } else {
    config = config || {};
  }

  // Merge request configuration. Merge this.defaults and config (the former is the default and the latter is custom).
  // mergeConfig when merging the two objects, if they have the same configuration item, the custom configuration is preferred.
  In other words, the custom configuration takes precedence over the default configuration.
  config = mergeConfig(this.defaults, config);

  // Set the request mode (default get)
  if (config.method) {
    config.method = config.method.toLowerCase();
  } else if (this.defaults.method) {
    config.method = this.defaults.method.toLowerCase();
  } else {
    config.method = 'get';
  }
  // Connect to interceptor middleware
  var chain = [dispatchRequest, undefined];
  The resolve(value) method returns a Promise object resolved with the given value.
  // Convert config to a Promise object to use a promise-based chain operation.
  var promise = Promise.resolve(config);

  // This is the big pity and the rejected function that you get from the Request interceptor.
  // Insert all of them into the header of the chain array. The last one to be added will be at the front of the array.
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });

  // This is the big pity and the rejected function that you get from the Response interceptor.
  // Add all of them to the end of the chain array. The last one is added to the end of the array (first-in, first-out).
  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });

  // Add request and response interceptors to the chain array.
  // [requestSuccess, requestError, dispatchRequest, undefined, requestSuccess, 
  // requestError], which is simplified and can actually have multiple requests and responses. The sequence fetched in the while loop
  // Will be removed from left to right. The request interceptor inserted into the chain array is executed first
  // First in, last out. Responder interceptors are first in, first out.
  
  // In this loop, two elements are continually fetched from the head of the chain array, the first of which is the resolve of the promise
  // reject method, the second as a reject method for promise. In this way, requests and responses added to the chain array can be co-directed
  // Interceptor corresponding. Also, under the chain operation promise.then, the order of execution will be as follows: request interceptor,
  // dispatchRequest and response interceptors are executed in this order. Finally, intercept before request and intercept after response.
  
  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }
  
    return promise;
};

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

// Provide aliases for supported request methods
utils.forEach(['delete'.'get'.'head'.'options'].function forEachMethodNoData(method) {
  Axios.prototype[method] = function (url, config) {
    return this.request(mergeConfig(config || {}, {
      method: method,
      url: url,
      data: (config || {}).data // config.data or {}. Data (i.e. undefined)
    }));
  };
});

utils.forEach(['post'.'put'.'patch'].function forEachMethodWithData(method) {
  Axios.prototype[method] = function (url, data, config) {
    return this.request(mergeConfig(config || {}, {
      method: method,
      url: url,
      data: data
    }));
  };
});

module.exports = Axios;
Copy the code

Reading the source code in axios.js, we see that it declares an Axios class and initializes the default configuration of Axios via constructors, request interceptors and response interceptors, and request methods such as request, GET, and POST.

It’s easy to understand through the Axios class that initializes the default configuration and provides aliases for the request method. However, for interceptors and dispatchRequest, there can be a bit of ambiguity. Let’s analyze them.

The interceptor

There are two types of interceptors: request interceptors and response interceptors. Request interceptors are used to perform some action before a request is sent. For example, modify configuration items. Response interceptors are used to perform some action upon receipt of a server response. For example, users are prompted for success or failure based on a status code. Having a general understanding is not enough, we also have to look at its source implementation, in order to understand.

/ / file location: lib/core/InterceptorManager js

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

// Interceptor class
function InterceptorManager() {
  this.handlers = [];
}

/** * adds a new interceptor (to the end of the array) to the handler (stack) and returns the current interceptor's subscript (this.handlers. Length-1) in the stack as id **@param {Function} This is a big pity that deals with the "then" of "Promise" *@param {Function} Reject (reject); reject (reject); reject (reject)@return {Number} The ID */ that will be used later to remove the interceptor

InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected
  });
  return this.handlers.length - 1;
};

/** * Removes an interceptor * from the stack@param {Number} Id (id returned by 'use') */
 
InterceptorManager.prototype.eject = function eject(id) {
  if (this.handlers[id]) {
    this.handlers[id] = null; }};/** * Iterate over all registered interceptors * This method is particularly useful for skipping interceptors that may become 'null' when 'eject' is called. *@param {Function} Fn calls the function */ for each interceptor
 
InterceptorManager.prototype.forEach = function forEach(fn) {
  // utils. ForEach (obj, callback) iterates through an array or object, calling a function forEach item.
  // The previous code has described its function and implementation, if you forget, can look forward to see.
  utils.forEach(this.handlers, function forEachHandler(h) {
    if(h ! = =null) { fn(h); }}); };module.exports = InterceptorManager;
Copy the code

By reading the source code above, we can see that interceptors implement three main methods.

  1. Use — Add interceptors
  2. Eject — Removes interceptors
  3. ForEach — Traverses and invokes interceptors

But only look at the source code, and not very intuitive let us understand it. The following code is a use case for the interceptor’s use and eject methods that I copied from the AXIos documentation (note: the forEach method is already in the AXIos source code).

// Add request interceptor
axios.interceptors.request.use(function (config) {
    // Do something before sending the request...
    return config;
}, function (error) {
    // Processing request error...
    return Promise.reject(error);
});
  
// Add a response interceptor
axios.interceptors.response.use(function (response) {
    // Process the response data...
    return response;
}, function (error) {
    // Handle response error...
    return Promise.reject(error);
});

// Remove interceptor
const myInterceptor = axios.interceptors.request.use(function () {
    // Do something...
});
axios.interceptors.request.eject(myInterceptor);
Copy the code

See here, you must have a deeper understanding of the interceptor. If you are still in doubt, read the source code of the interceptor and how to use it a few times. Of course, it’s better to download the axios source code and debug it as you go along.

DispatchRequest Dispatches requests

DispatchRequest is where Axios actually makes the request. It uses the configured adapter to send requests to the server namely the network request module for network requests. Before each request, it determines whether the request has been canceled. If not, the request data is converted, the HEADERS request is merged, and so on. Finally, the network request is executed and the response data is transformed (whether the response is successful or not).

/ / file location: lib/core/dispatchRequest js

var utils = require('. /.. /utils');
var transformData = require('./transformData');
var isCancel = require('.. /cancel/isCancel');
var defaults = require('.. /defaults');

// Throw 'cancel' if cancellation has been requested
function throwIfCancellationRequested(config) {
  if(config.cancelToken) { config.cancelToken.throwIfRequested(); }}/** * use the configured adapter to send requests to the server. * *@param {object} Config Specifies the configuration for the request *@returns {Promise} The Promise to implement */
module.exports = function dispatchRequest(config) {
  throwIfCancellationRequested(config);

  // Make sure headers exists
  config.headers = config.headers || {};

  // Convert the request data
  config.data = transformData(
    config.data,
    config.headers,
    config.transformRequest
  );

  // Flatten headers
  config.headers = utils.merge(
    config.headers.common || {},
    config.headers[config.method] || {},
    config.headers
  );
  
  // Delete request mode from headers
  utils.forEach(
    ['delete'.'get'.'head'.'post'.'put'.'patch'.'common'].function cleanHeaderConfig(method) {
      deleteconfig.headers[method]; });// Set the adapter. The request configuration takes precedence over the default configuration and is not configured in the request configuration
  // Adapter, instead use the default. Are environment, using XHR Adapter. The node environment
  // Use HTTP Adapter.
  var adapter = config.adapter || defaults.adapter;

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

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

    return response;
  }, function onAdapterRejection(reason) {
    // isCancel returns a Boolean value that determines whether a cancellation request is made.
    // If it is true, the request is cancelled, false is not.
    if(! isCancel(reason)) { throwIfCancellationRequested(config);// Convert the 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

The adapter adapter

There are two types of adapters: XHR (XMLHttpRequest) Adapter in the browser environment and HTTP Adapter in the Node environment. Both use Promise encapsulation, which is why AXIos is called a Promise based HTTP request library.

Since the environment in which AXIOS is used for front-end development is usually a browser, this article focuses on the XHR Adapter, or XMLHttpRequest object. About its usage and function, you can go to the Web APIs | MDN learning. As for the HTTP Adapter, you can read the source code to learn.

// File location: lib/adapters/xhr.js

var utils = require('. /.. /utils');
var settle = require('. /.. /core/settle');
var cookies = require('. /.. /helpers/cookies');
var buildURL = require('. /.. /helpers/buildURL');
var buildFullPath = require('.. /core/buildFullPath');
var parseHeaders = require('. /.. /helpers/parseHeaders');
var isURLSameOrigin = require('. /.. /helpers/isURLSameOrigin');
var createError = require('.. /core/createError');

module.exports = function xhrAdapter(config) {
  return new Promise(function dispatchXhrRequest(resolve, reject) {
    var requestData = config.data;
    var requestHeaders = config.headers;

    if (utils.isFormData(requestData)) {
      delete requestHeaders['Content-Type']; // Delete to allow the browser to set it
    }

    var request = new XMLHttpRequest();

    HTTP basic authentication
    if (config.auth) {
      var username = config.auth.username || ' ';
      var password = config.auth.password ? unescape(encodeURIComponent(config.auth.password)) : ' ';
      requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password);
    }

    var fullPath = buildFullPath(config.baseURL, config.url);
    request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);

    // Set request timeout (ms)
    request.timeout = config.timeout;

    // Listen for ready state
    request.onreadystatechange = function handleLoad() {
      if(! request || request.readyState ! = =4) {
        return;
      }

      // The request failed and we did not get a response. This will be replaced by onError
      // With one exception: the request uses file: protocol, most browsers
      // Will return a status of 0, even if it was a successful request
      if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') = = =0)) {
        return;
      }

      // Prepare a response
      var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null;
      varresponseData = ! config.responseType || config.responseType ==='text' ? request.responseText : request.response;
      var response = {
        data: responseData,
        status: request.status,
        statusText: request.statusText,
        headers: responseHeaders,
        config: config,
        request: request
      };

      settle(resolve, reject, response);

      // Cleanup request
      request = null;
    };

    // Handle browser request cancellations (as opposed to manual cancellations)
    request.onabort = function handleAbort() {
      if(! request) {return;
      }

      reject(createError('Request aborted', config, 'ECONNABORTED', request));

      // Cleanup request
      request = null;
    };

    // Handle low-level network errors
    request.onerror = function handleError() {
      // The real error is hidden by the browser
      // onError is only raised when there is a network error
      reject(createError('Network Error', config, null, request));

      // Cleanup request
      request = null;
    };

    // Processing timed out
    request.ontimeout = function handleTimeout() {
      var timeoutErrorMessage = 'timeout of ' + config.timeout + 'ms exceeded';
      if (config.timeoutErrorMessage) {
        timeoutErrorMessage = config.timeoutErrorMessage;
      }
      reject(createError(timeoutErrorMessage, config, 'ECONNABORTED',
        request));

      // Cleanup request
      request = null;
    };

    // Add the XSRF header file
    // This is only done when running in standard browser environments.
    // This is especially not necessary if we are in a Web worker or react-native.
    if (utils.isStandardBrowserEnv()) {
      var xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath)) && config.xsrfCookieName ?
        cookies.read(config.xsrfCookieName) :
        undefined;

      if(xsrfValue) { requestHeaders[config.xsrfHeaderName] = xsrfValue; }}// Add the request header
    if ('setRequestHeader' in request) {
      utils.forEach(requestHeaders, function setRequestHeader(val, key) {
        if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') {
          // Delete content-type if the data is undefined
          delete requestHeaders[key];
        } else {
          // Otherwise add the request headerrequest.setRequestHeader(key, val); }}); }// Add withCredentials to request, if necessary
    if(! utils.isUndefined(config.withCredentials)) { request.withCredentials = !! config.withCredentials; }// Add responseType to request if needed
    if (config.responseType) {
      try {
        request.responseType = config.responseType;
      } catch (e) {
        // Expected DOMException thrown by xmlHttprequest-incompatible browsers
        // However, for the 'json' type, this can be suppressed because it can be parsed by the default 'transformResponse' function.
        if(config.responseType ! = ='json') {
          throwe; }}}// Process the progress if necessary
    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);
    }

    if (config.cancelToken) {
      // Handle cancellation
      config.cancelToken.promise.then(function onCanceled(cancel) {
        if(! request) {return;
        }

        request.abort();
        reject(cancel);
        // Clear the request
        request = null;
      });
    }

    if(! requestData) { requestData =null;
    }

    // Send the request
    request.send(requestData);
  });
};
Copy the code

conclusion

The above is my shallow interpretation of AXIos source code, only related to its execution process, I hope it can be helpful to students learning source code. Besides, my personal expression ability is not very good. If I make mistakes, I hope you can correct me and make progress together.