What is Axios 🐎?

Axios is a concise, easy-to-use, and efficient code wrapper library for managing HTTP requests based on Promises. In layman’s terms, it’s a front-end alternative to Ajax, and you can use it to initiate HTTP requests. It’s based on Promises and can manage asynchronous operations better than Ajax callback functions. The source address

The main features of Axios

  • Based on the Promise
  • Supports browsers and Node.js environments
  • You can add request, response interceptors, and transform request and response data
  • Requests can be cancelled or interrupted
  • Automatically convert JSON data
  • The client supports XSRF defense

Source directory structure and main file function descriptionBased on version 0.21.4

| – /lib/ #

| | – /adapters/ # Defines the adapter that sends the request

| | | — HTTP. Js # node environment HTTP object

| | | — xhr.js # XML object for browser environment

| | – /cancel/ #

Helpers | | – /helpers/ #

| | – /core/ #

| | | — axios.js # Axios instance constructor

| | | — createError. Js # throw error

| | | — dispatchRequest.js # is used to call the HTTP request adapter method to send the request

| | | interceptorManager. js # interceptorManager. js

| | | — mergeConfig. Js # mergeConfig

# change the state of the Promise based on the HTTP response status

| | | — transformdata.js # transformData format

| | -axios.js # entry to create a constructor

| | -defaults. js #

| | -utils

From the entrance

We open up /lib/axios.js and start parsing from the entry.

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

// The method to create an axios instance
function createInstance(defaultConfig) {
  // Build a context object based on the default configuration, including the default configuration and request, corresponding interceptor object
  var context = new Axios(defaultConfig);
  // Creating bind returns a function and the context points to the context
  var instance = bind(Axios.prototype.request, context);
  // Copy prototype to instance similar to the method used in Axios prototype (e.g. Request, get, post...) Inheriting to the instance, this points to context
  utils.extend(instance, Axios.prototype, context);
  // Copy the context object properties (default configuration and request, corresponding interceptor object) to the instance. This points to context
  utils.extend(instance, context);
  
  // Create an axios instance, which should be used in axiOS encapsulation.
  instance.create = function create(instanceConfig) {
    // mergeConfig is used for deep merges
    return createInstance(mergeConfig(defaultConfig, instanceConfig));
  };

	// Return the instance
  return instance;
}

// Create instance defaulst with the default configuration
var axios = createInstance(defaults);

// Expose the Axios class for inheritance (I haven't used it yet)
axios.Axios = Axios;

// This throws an interrupt/cancel request related method into the entry object
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');

// Make concurrent requests with full promise capabilities
axios.all = function all(promises) {
  return Promise.all(promises);
};
// Use with axios.all to convert a single parameter group to multiple parameters =====> more details!!
axios.spread = require('./helpers/spread');

// used to monitor whether an error was thrown for Axios
axios.isAxiosError = require('./helpers/isAxiosError');
/ / export
module.exports = axios;

// Allow default export in TS
module.exports.default = axios;
Copy the code

createInstance

Through the analysis of the import files, we can find:

  • We use it directly in normal developmentaxios.create()Build instances and directaxios()Both are passedcreateInstanceIt’s constructed by this function.

This function does a few things:

  1. The first step is to build a context object based on the default configuration, including the default configuration and request, and corresponding interceptor objects
  2. The bind instance is created to return a letter, so we can use it axios(config) Use it this way, and the context points to the context
  1. Copying prototype to an instance is similar to copying Axios prototype methods (e.g. Request, get, POST…) Inherit to the instance and use it Axios. The get (), axios. Post () This refers to context
  2. Copy the context object properties (default configuration and request, corresponding interceptor object) to the instance, this pointing to context
  1. Return instance (instance as function)

axios.create

A PR update for the axios.create method was issued on September 5, 2021 to provide deep construction controller capability for large applications, or for multi-domain use of multiple instances, by encapsulating constructs against already constructed instances: See this PR for details

  • axiosOften usedapiThe request method is tagged with the following line of codeaxiosOn the.

/lib/core/ axios.js

// Master request method All requests eventually point to this method
Axios.prototype.request =  function request(config) {}// The following information will be explained
// Get the completed request URL method
Axios.prototype.getUri = function getUri(config) {};// Attach the normal request (no body data) to Prototype
utils.forEach(['delete'.'get'.'head'.'options'].function forEachMethodNoData(method) {
  Axios.prototype[method] = function(url, config) {
    // Both end up calling the request method
    return this.request(mergeConfig(config || {}, {
      method: method,
      url: url,
      data: (config || {}).data
    }));
  };
});
// Attach the request with body data to Prototype
utils.forEach(['post'.'put'.'patch'].function forEachMethodWithData(method) {
  Axios.prototype[method] = function(url, data, config) {
    // Both end up calling the request method
    return this.request(mergeConfig(config || {}, {
      method: method,
      url: url,
      data: data
    }));
  };
});
Copy the code

Axios.prototype has nine methods hanging on it, including the ones we use below.

axios.request(config)
axios.get(url[, config])
axios.delete(url[, config])
axios.head(url[, config])
axios.options(url[, config])
axios.post(url[, data[, config]])
axios.put(url[, data[, config]])
axios.patch(url[, data[, config]])
Copy the code

Request methods are divided into two types of traversal, which is attached to prototype because the last three methods may have request bodies and input parameter forms are different, so they should be handled separately.


Axios.prototype.request

Next we go to the core request method axios.prototype. request, which is the core skeleton of the whole Axios request, mainly doing adaptation to different config, and the key core chain call implementation. Let’s go into the code and see:

Axios.prototype.request = function request(config) {
  Axios ('url',config)/axios(config)
  if (typeof config === 'string') {
    config = arguments[1) | | {}; config.url =arguments[0];
  } else {
    config = config || {};
  }
	// Merge the default configuration
  config = mergeConfig(this.defaults, config);

  // Convert the request method to lowercase
  if (config.method) {
    config.method = config.method.toLowerCase();
  } else if (this.defaults.method) {
    config.method = this.defaults.method.toLowerCase();
  } else {
    config.method = 'get';
  }

  var transitional = config.transitional;
	
  if(transitional ! = =undefined) {
    // Transitional configuration will be removed after version 1.0.0.
    validator.assertOptions(transitional, {
      silentJSONParsing: validators.transitional(validators.boolean, '1.0.0'),
      forcedJSONParsing: validators.transitional(validators.boolean, '1.0.0'),
      clarifyTimeoutError: validators.transitional(validators.boolean, '1.0.0')},false);
  }
	
  / /... The following content has a relatively large update, separate out the detailed explanation !!!!
 
};
Copy the code

Promise chains form

Let’s take a look at the classic code that makes up the promise chain:

  // Create an array of stored chained calls with the core call method dispatchRequest in the first place and empty second place
  var chain = [dispatchRequest, undefined];
	Resolve (config) because the request interceptor executes first, so setting the request interceptor gets all the config configuration for each request
  var promise = Promise.resolve(config);
	// Put the set request interceptor success handler and failure handler at the top of the array
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });
	// Put the success and failure handlers of the response interceptor at the end of the array
  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });
	// The loop takes two at a time to form the promise chain
  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }
	/ / return promise
  return promise;
Copy the code

To make it clear, the entire promise chain can be interpreted as executing from left to right:

Request interceptor ===> Request === => Response interceptor


A new PR

Here’s a new PR 6 months ago that reconstructs the logic of this part of the code. It’s a big PR. Bear with me:

Here mainly for the asynchronous request interceptor is likely to happen, or a long macro task execution, and refactor the code before, because the request on the micro tasks performed, the timing of the micro task creation before build promise chain, if when performing macro task takes longer, before the request or an asynchronous request interceptor do, This causes a delay in the actual Ajax request being sent, so it is necessary to resolve this issue.

 	// Request interceptors store arrays
  var requestInterceptorChain = [];
	// All request interceptors are synchronous by default
  var synchronousRequestInterceptors = true;
	// Iterate over the array of registered request interceptors
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    // Where interceptor is registered on every interceptor object axios requests that the interceptor expose the runWhen configuration for interceptors that require runtime detection to execute
    // If the function is configured and returns true, the result is logged to the interceptor chain, otherwise the layer loop is terminated
    if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {
      return;
    }
		// interceptor.synchronous is an external configuration that identifies whether the interceptor is asynchronous or synchronous. The default is false(asynchronous)
    If one of the request interceptors is asynchronous, then the following promise chain will be executed differently
    synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;
		// into the request interceptor array
    requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
  });
	// The corresponding interceptor stores an array
  var responseInterceptorChain = [];
	// Traversal is pushed sequentially into the interceptor storage array
  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
  });

  var promise;
	// If it is asynchronous, it is the default
  if(! synchronousRequestInterceptors) {// This is the same logic as before the refactoring
    var chain = [dispatchRequest, undefined];
    // Request the interceptor plug to the front
    Array.prototype.unshift.apply(chain, requestInterceptorChain);
    // The response interceptor is plugged back
    chain = chain.concat(responseInterceptorChain);
    promise = Promise.resolve(config);
   	// Execute the loop
    while (chain.length) {
      promise = promise.then(chain.shift(), chain.shift());
    }
		/ / return promise
    return promise;
  }

	// Here is the synchronization logic
  var newConfig = config;
	// The request interceptor returns the latest config before the request one by one
  while (requestInterceptorChain.length) {
    var onFulfilled = requestInterceptorChain.shift();
    var onRejected = requestInterceptorChain.shift();
    // If an exception is caught, it is thrown directly
    try {
      newConfig = onFulfilled(newConfig);
    } catch (error) {
      onRejected(error);
      break; }}// By not creating a microtask prematurely, the current macro task is too long, or there is an asynchronous task in one of the request interceptors that blocks true request latency
  try {
    promise = dispatchRequest(newConfig);
  } catch (error) {
    return Promise.reject(error);
  }
	// Response interceptor execution
  while (responseInterceptorChain.length) {
    promise = promise.then(responseInterceptorChain.shift(), responseInterceptorChain.shift());
  }

  return promise;
Copy the code

/core/InterceptorManager.js

// Interceptor adds two configuration parameters synchronous and runWhen
InterceptorManager.prototype.use = function use(fulfilled, rejected, options) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected,
    // They are assumed to be asynchronous by default if your request interceptor is synchronous, you can default with this parameter, which tells AXIOS to run the code synchronously and avoid any delay in request execution.
    synchronous: options ? options.synchronous : false.// If you want to execute a specific interceptor based on runtime checks, you can use the runWhen parameter, which is of type function
    runWhen: options ? options.runWhen : null
  });
  return this.handlers.length - 1;
};
Copy the code

The above content needs to be combed repeatedly, the author is also combined with the source code and the discussion of the RECONSTRUCTION of the PR carried out a careful analysis: details see this PR!!


Specific change comparison diagram

Interceptor implementation

In our actual use of AXIos, request and corresponding interceptors are often used, which is one of the characteristics of AXIos. In the previous section, we analyzed the composition of the promise chain and when the interceptor was created, which we constructed in axios.create when createInstance went to new Axios instance. Directly on the code:

function Axios(instanceConfig) {
  this.defaults = instanceConfig; The request and response interceptors created here are constructed from a common classthis.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}
Copy the code

Let’s enter/core/InterceptorManager in js:

function InterceptorManager() {
  this.handlers = [];
}
// Add interceptor add success or failure callback
InterceptorManager.prototype.use = function use(fulfilled, rejected, options) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected,
    synchronous: options ? options.synchronous : false.runWhen: options ? options.runWhen : null
  });
  return this.handlers.length - 1;
};

// Unregister the specified interceptor
InterceptorManager.prototype.eject = function eject(id) {
  if (this.handlers[id]) {
    this.handlers[id] = null; }};// Iterate over the execution
InterceptorManager.prototype.forEach = function forEach(fn) {
  utils.forEach(this.handlers, function forEachHandler(h) {
    // Make sure eject is not logged out before execution
    if(h ! = =null) { fn(h); }}); };module.exports = InterceptorManager;
Copy the code

The implementation of interceptor is relatively simple. Through a unified model, a unified controller is constructed to manage the registration, deregistration and execution of interceptors.

dispatchRequest

Let’s go to the core request method dispatchRequest, where the structure looks relatively simple:

    • Process the request header config configuration
    • calladapterThe adapter makes real requests, ajax requests for the browser environment and HTTP requests for the Node environment
    • Construct the response data, which automatically transforms the JSON data
 function dispatchRequest(config) {
	// Cancel the request early
  throwIfCancellationRequested(config);

  // Assign a default value
  config.headers = config.headers || {};

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

  // Merge the headers configuration
  config.headers = utils.merge(
    config.headers.common || {},
    config.headers[config.method] || {},
    config.headers
  );
	// Delete redundant merged data
  utils.forEach(
    ['delete'.'get'.'head'.'post'.'put'.'patch'.'common'].function cleanHeaderConfig(method) {
      deleteconfig.headers[method]; });// The adapter Axios supports both node side and browser side
  var adapter = config.adapter || defaults.adapter;
	// Execute the request
  return adapter(config).then(function onAdapterResolution(response) {
    // Cancel the request early
    throwIfCancellationRequested(config);
    // Do data conversion
    response.data = transformData.call(
      config,
      response.data,
      response.headers,
      config.transformResponse
    );
    return response;
  }, function onAdapterRejection(reason) {
    if(! isCancel(reason)) { throwIfCancellationRequested(config);// Do data conversion
      if(reason && reason.response) { reason.response.data = transformData.call( config, reason.response.data, reason.response.headers, config.transformResponse ); }}return Promise.reject(reason);
  });
};
Copy the code

The adapter adapter

Classic design pattern: adaptor pattern application.

function getDefaultAdapter() {
  var adapter;
  // Check whether the XMLHttpRequest object exists to represent the browser environment
  if (typeofXMLHttpRequest ! = ='undefined') {
    // For browsers use XHR adapter
    adapter = require('./adapters/xhr');
    // The Node environment uses native HTTP to initiate requests
  } else if (typeofprocess ! = ='undefined' && Object.prototype.toString.call(process) === '[object process]') {
    adapter = require('./adapters/http');
  }
  return adapter;
}
Copy the code

./ Adapters /xhr.js encapsulates the native Ajax XMLHttpRequest object../ Adapters/HTTP. js encapsulates the Node HTTP module and handles HTTPS accordingly. Specific packaging details of various boundary details have done special processing, because we still use more in the browser daily, simple XHR packaging source code to do some overall.

Axios voluntarily cancels the request

How to use

Here’s how the next cancel request is used:

import { CancelToken } from axios;

// Source is an object with the structure {token, cancel}
// Token is used to indicate a request, which is a promise
// Cancel is a function that, when called, cancels the request for token injection
const source = CancelToken.source();

axios
    .get('/user', {
  			// Inject the token into the request
        cancelToken: source.token, 
    })
    .catch(function (thrown) {
        // Determine if this is due to active cancellation
        if (axios.isCancel(thrown)) {
            console.log('Voluntary cancellation', thrown.message);
        } else {
            console.error(thrown); }});// Call the cancel method here, which interrupts the request whether it returns successfully or not
source.cancel('I voluntarily cancel the request')
Copy the code

Source code analysis

In lib/axios.js, the axios instance throws three interfaces to cancel the request. Let’s look at three files involved in canceling the request.

Cancel.js: The Cancel function (fake class) accepts the argument message which is actually the argument in calling source.cancel() : Cancel message. The __CANCEL__ attribute on the prototype object identifies the message returned by the cancellation request as the message returned by the cancellation request

Canceltoken. js: CancelToken provides the ability to create a token instance to register a cancellation request and provides a cancellation request method

3. Iscancer.js: Used to determine whether it is the result returned for the cancellation request, i.e. whether it is an instance of Cancel

Let’s take a look at CancelToken’s source code, from an execution perspective:

1. The source method

// The token and cancel cancellation methods are exposed
CancelToken.source = function source() {
  var cancel;
  // Create an instance of CancelToken with two attributes: a promise and a Reason
  // Call (cancel); // Call (cancel);
  var token = new CancelToken(function executor(c) {
    cancel = c;
  });
  return {
    token: token,
    cancel: cancel
  };
};
Copy the code

The source method returns an object with two attributes: The token is an instance of new CancelToken, and cancel is an argument to the executor function of New CancelToken. It is a function used to call the active cancellation request when needed. Let’s examine CancelToken’s source code.

CancelToken constructor

function CancelToken(executor) {
  // Type judgment
  if (typeofexecutor ! = ='function') {
    throw new TypeError('executor must be a function.');
  }
	// Create an instance of promise
  var resolvePromise;
  this.promise = new Promise(function promiseExecutor(resolve) {
  This. Promise state will become a pity when the resolvePromise is implemented
    resolvePromise = resolve;
  });
	/ / save this
  var token = this;
  // Cancel = c; // Cancel = c; // Cancel = c;
  // This exposes the cancel function, leaving it up to the user to cancel and the user to execute the logic inside the function when calling cancel
  executor(function cancel(message) {
    // The request has been cancelled
    if (token.reason) {
      return;
    }
		Call new Cancel to construct an instance of Cancel information
    token.reason = new Cancel(message);
    // This. Promise instance state will be fulfilled, and the message will be Reason (new Cancel instance).
    resolvePromise(token.reason);
  });
}
Copy the code

A promise instance will be created in CancelToken, and a Reason will store the cancellation information. When the user calls source.cancel(message), the state of the promise instance will be changed to depressing, Create an instance of the Reason error message with the __CANCEL__ attribute, which identifies it as the message returned by the cancellation request.

3. How is the request handled!!

Operations within the Adapter

How do we interrupt/cancel the request when we call the cancel method? This piece of code in the adapter will find the desired answer. The source address

// Check whether the user has configured the cancellation token in the request modification
if (config.cancelToken) {
  // If configured, then the promise on the instance is used to handle the logic when the cancel method is called
  // If the Ajax request is sent before we register the.then for the cancelToken promise
  // When we call the Cancel method, the promise of the cancelToken instance will become a pity state, and the logic in. Then will be fulfilled
  config.cancelToken.promise.then(function onCanceled(cancel) {
    if(! request) {return;
    }
		// Call native Abort to cancel the request
    request.abort();
    // The axios promise instance enters the Rejected state
    reject(cancel);
    // Request is null
    request = null;
  });
}
// The real request is sent at this point!!
request.send(requestData);
Copy the code

This is how AXIos interrupts the request. In other cases, it can cancel the request before and after the request is completed. This can also avoid unnecessary request sending and unnecessary logic execution. Let’s first look at the throwIfRequested method on the CancelToken prototype:

// There is a simple way to CancelToken by simply throwing reason
// Reason is an instance of the new Cancel argument that calls cancel
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
  if (this.reason) {
    throw this.reason; }};Copy the code

In our core request method dispatchRequest:

If you throw an error, the state of the axios constructed promise instance will be set to rejected, so you can go directly to. Catch

// Decide to throw if the request cancellation token is configured
function throwIfCancellationRequested(config) {
  if (config.cancelToken) {
    // Call the method that throws the errorconfig.cancelToken.throwIfRequested(); }}module.exports = function dispatchRequest(config) {
  / / request before
  throwIfCancellationRequested(config);
  / /... Omit code
  // The request is in the above adapter
  return adapter(config).then(function onAdapterResolution(response) {
    // After the request is complete
    throwIfCancellationRequested(config);
   	/ /... Omit code
  }, function onAdapterRejection(reason) {
    // After the request is complete
    if(! isCancel(reason)) { throwIfCancellationRequested(config);/ /... Omit code
    }
    return Promise.reject(reason);
  });
};
Copy the code

We’re going to use the isCancel method in the AXIos request catch to determine if this exception is thrown by the Cancel request, that is, if it’s an instance of Cancel, and do something about it.



Let’s take a brief look at how requests are cancelled:

I believe that after a review of the above source analysis and flow chart analysis, should be able to cancel the principle of the request to have a rough understanding, the entire implementation process, details clear also need to read repeatedly.

Other small points

Concurrent ability

In the official axios, two methods axios.all and axios.spread are also provided, mainly for executing multiple concurrent requests, as follows:

function getUserAccount() {
  return axios.get('/user/12345');
}

function getUserPermissions() {
  return axios.get('/user/12345/permissions');
}

axios.all([getUserAccount(), getUserPermissions()])
  .then(axios.spread((acct, perms) = > {
    // The logic in the callback is executed only after both requests are completed
  }));
Copy the code

Let’s look directly at the source code:

1. The axios.all method is exactly the same as the promise. all method, which is directly called promise. all

2. The axios.spread method takes a function as an argument that is also the response to all requests

// Make concurrent requests with full promise capabilities
axios.all = function all(promises) {
  return Promise.all(promises);
};
// Accept a function callback
axios.spread = function spread(callback) {
  // Return a new function, arr, which returns an array successfully
  return function wrap(arr) {
    // Return the result of the concurrent request to callback to facilitate the processing of the data returned by the concurrent request together as in the above example
    return callback.apply(null, arr);
  };
};
Copy the code

Tool function

The merge function is used to merge requests for config configuration information in a similar way to the deep copy deepClone function

function merge(/* obj1, obj2, obj3, ... * /) {
  var result = {};
  // Closures handle logical functions
  function assignValue(val, key) {
    // Result has this key value and is the same type of common Object recursive merge
    if (isPlainObject(result[key]) && isPlainObject(val)) {
      result[key] = merge(result[key], val);
      // result does not have an assignment
    } else if (isPlainObject(val)) {
      result[key] = merge({}, val);
      // Array type
    } else if (isArray(val)) {
      result[key] = val.slice();
      // Other types are directly assigned
    } else{ result[key] = val; }}// loop the call to the input parameter
  for (var i = 0, l = arguments.length; i < l; i++) {
    forEach(arguments[i], assignValue);
  }
  // Return the merged result
  return result;
}
Copy the code

Extend function: inside AXIOS to attach some built-in properties and built-in methods to axiso instances

function extend(a, b, thisArg) {
  // Loop b's properties hang on A
  forEach(b, function assignValue(val, key) {
    // If there is a specific this pointing to and the type is function
    if (thisArg && typeof val === 'function') {
      // Bind returns a function that uses apply internally
      a[key] = bind(val, thisArg);
    } else {
      // Assign directlya[key] = val; }});return a;
}
Copy the code

conclusion

This article focus on the main source of the axios are at the 👆 steps one by one, if you read carefully believe there will be some good harvest, direct reading the source code is a hard way, hope that through a combination of the source code with this article, can help the readers better, faster to understand axios source, and later in the development of better use of axios.


Write in the last

If there are any errors in this article, please correct them in the comments section. If this article helped you, please like 👍 and follow ❤️