This is the fourth day of my participation in the November Gwen Challenge. See details: The last Gwen Challenge 2021.

Axios introduction

Axios is a Promise-based HTTP library that can be used in browsers and Node.js and supports the following features:

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

Related documents: NPM GitHub

Source directory

├ ─ ─ / lib /// Project source code└ ─ ─ / adapters// Define the adapter that sends the request├ ─ ─ HTTP. Js// Node environment HTTP object├ ─ ─ XHR. Js// The browser environment XML object└ ─ ─ / cancel /// Define the cancel request function└ ─ ─ / helpers// Auxiliary methods└ ─ ─ / core /// Core functions├ ─ ─ Axios. Js// The axios instance constructor├ ─ ─ createError. Js// Throw an error├ ─ ─ dispatchRequest. Js// Call the HTTP request adapter method to send the request├ ─ ─ InterceptorManager. Js// Interceptor manager├ ─ ─ mergeConfig. Js// Merge parameters├ ─ ─ settle. Js// Change the state of the Promise based on the HTTP response status├ ─ ─ transformData. Js// Convert to data format└ ─ ─ axios. Js// create a constructor└ ─ ─ defaults. Js// Default configuration└ ─ ─ utils. Js// Public utility functions
Copy the code

(1) Start from the entrance

Axios provides a function createInstance to assist in creating an instance of the Axios class. Note, however, that instead of returning an Axios instance object, this function returns the instance object’s Request method, and mounts the instance object’s other alias methods to the Request method (functions are also objects, and attribute methods can be added). Hence the following usage:

axios({... }); axios.get('/', {... })...Copy the code
 // The method to create an axios instance
 function createInstance(defaultConfig) {
   // Generate an axiOS instance according to the default configuration
   var context = new Axios(defaultConfig);
   // Create an instance and return request, where this refers to context
   var instance = bind(Axios.prototype.request, context);
 ​
   // Inherits the Axios prototype method from instance. The inner bind makes this point to context
   utils.extend(instance, Axios.prototype, context);
 ​
   // Copy context object properties (default configuration and request, corresponding interceptor object) to the instance
   utils.extend(instance, context);
 ​
   // The factory function that creates the instance should be used in axiOS packaging (we put some default, common configuration on the instance, reuse the instance, no need to recreate the instance each time).
   instance.create = function create(instanceConfig) {
     return createInstance(mergeConfig(defaultConfig, instanceConfig));
   };
 ​
   // Return the instance object
   return instance;
 }
 ​
 // Create an instance
 var axios = createInstance(defaults);
 ​
 // Also provides some other methods for the exported axios:
 ​
 // Mount the original Axios class, which can be used for inheritance
 axios.Axios = Axios;
 ​
 // Interrupt/cancel the request
 axios.Cancel = require('./cancel/Cancel');
 axios.CancelToken = require('./cancel/CancelToken');
 axios.isCancel = require('./cancel/isCancel');
 axios.VERSION = require('./env/data').version;
 ​
 // Concurrent requests, full promise
 axios.all = function all(promises) {
   return Promise.all(promises);
 };
 axios.spread = require('./helpers/spread');
 ​
 // used to monitor whether an error was thrown for Axios
 axios.isAxiosError = require('./helpers/isAxiosError');
 / / export
 module.exports = axios;
 ​
 // Allows default exports in TypeScript
 module.exports.default = axios;
Copy the code

When we reference the Axios library, it internally calls createInstance to initialize and return Request

We can see that we normally use axios() and axios.create() to create instances by calling createInstance.

So, we can create another AXIos request using the factory function:

  // Use the default request
  axios.get('/user');
 ​
  // Use the new configuration to send the request
  let newRequest = axios.create({baseURL: 'http://localhost:9999'});
  newRequest.get('/user');
Copy the code

(2)Axios class

The Axios class is the core class that encapsulates and provides the API used by the request.

 //Axios.js
 function Axios(instanceConfig) {
   this.defaults = instanceConfig;
   this.interceptors = {
     request: new InterceptorManager(),
     response: new InterceptorManager()
   };
 }
 /** * send a request **@param {Object} config The config specific for this request (merged with this.defaults)
  */
 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);
 ​
   / / set the 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';
   }
 ​
   var transitional = config.transitional;
 ​
   if(transitional ! = =undefined) {
     validator.assertOptions(transitional, {
       silentJSONParsing: validators.transitional(validators.boolean),
       forcedJSONParsing: validators.transitional(validators.boolean),
       clarifyTimeoutError: validators.transitional(validators.boolean)
     }, false);
   }
    // The key chain call, which is specifically resolved at.... later in the article
 };
Copy the code

Axios also provides a list of HTTP method alias functions based on the Request method and mounts them to the prototype:

  // Provide aliases for supported request methods
  // Encapsulate requests that do not need to submit body data
  utils.forEach(['delete'.'get'.'head'.'options'].function forEachMethodNoData(method) {
    /*eslint func-names:0*/
    Axios.prototype[method] = function(url, config) {
      return this.request(mergeConfig(config || {}, {
        method: method,
        url: url,
        data: (config || {}).data
      }));
    };
  });
  // Handle requests that can submit body data
  utils.forEach(['post'.'put'.'patch'].function forEachMethodWithData(method) {
    /*eslint func-names:0*/
    Axios.prototype[method] = function(url, data, config) {
      return this.request(mergeConfig(config || {}, {
        method: method,
        url: url,
        data: data
      }));
    };
  }); 
Copy the code

(3) Configuration processing

There are three configuration points in Axios:

  • Request method configuration
  • The instance configuration
  • Global configuration

Reference: axios-http.com/zh/docs/req…

(1) Request method configuration

Is the configuration passed in request and alias methods such as GET and POST

  axios({
    url: '/user'
  });
  axios.get('/user', {
    params: {
      page:1.limit:2}})...Copy the code

(2)Instantiate configuration

We can also pass in the base configuration at instantiation time (we can pass in some of the requested common configurations at instantiation time)

  let newRequest = axios.create({
    baseURL: 'http://localhost:9999'
  });
Copy the code

(3)Global (default) configuration

Axios also has a set of default configuration items, which are used if none are passed in at instantiation time or if the instantiation axios exports by default.

  // The default configuration can be obtained via axios.defaults
  axios.defaults.baseURL = 'http://localhost:8888';
  axios.get('/user');
Copy the code

Configuring a Priority

Request Configuration > Instance Configuration > Default Configuration

(4) Application and implementation of interceptors

There is a middleware-like mechanism in AXIos to handle tasks before the Request method requests them and after the response (before the user code executes).

 // 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) {
     // all status codes in the 2xx range trigger this function.
     // What to do with the response data
     return response;
   }, function (error) {
     // Any status code outside the 2xx range triggers this function.
     // Do something about the response error
     return Promise.reject(error);
   });
Copy the code

Reference: axios-http.com/zh/docs/int…

Interceptor implementation

The implementation of interceptors is relatively simple, essentially resembling middleware arrays with two groups: requests and responses. Through the unified model, a unified controller is constructed to manage the registration, logout, and execution of interceptors.

 // Axios.js
 function Axios(instanceConfig) {
    this.defaults = instanceConfig;
    // The request and response interceptors created here are constructed from a unified class
    this.interceptors = {
      request: new InterceptorManager(),
      response: new InterceptorManager()
    };
  }
 ​
 // core/InterceptorManager.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,
     // 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;
 };
 ​
 // 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

How interceptors work

 // Axios.js
  Axios.prototype.request = function request(config) {
    // Key chain calls
    // Chain call storage array -- request array
    var requestInterceptorChain = [];
    // All request interceptors are synchronous by default
    var synchronousRequestInterceptors = true;
    // Iterate over the array of registered interceptors
    this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
      // Here interceptor is each interceptor object registered
      // The AXIos request interceptor exposes the runWhen configuration for interceptors that require runtime detection to execute
      // For example, implement an interceptor that only gets
      // 
      // function onGetCall(config) {
      // return config.method === 'get';
      // }
      // axios.interceptors.request.use(function (config) {
      // config.headers.test = 'special get headers';
      // return config;
      // }, null, { runWhen: onGetCall });
      // 
      // If the function is configured and returns true, it is logged to the interceptor chain
      // If not, end the loop
      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;
 ​
      // This is a big surprise when the interceptor. This is a big surprise when the interceptor
      requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
    });
  
    // Chain call storage array -- response array
    var responseInterceptorChain = [];
    this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
      This is a big pity. Interceptor. Rejected joins the end of the chain
      responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
    });
  
    var promise;
  
    // If it is asynchronous, it is the default
    if(! synchronousRequestInterceptors) {// The dispatchRequest method is explained in the adapter section
      var chain = [dispatchRequest, undefined];
      // Request the interceptor plug to the front
      Array.prototype.unshift.apply(chain, requestInterceptorChain);
      // The response interceptor is plugged back
      chain.concat(responseInterceptorChain);
       //chain = [fulfilled[1],rejected[1],fulfilled[0],rejected[0],dispatchRequest, undefined,fulfilled[3],rejected[3],fulfilled[4],rejected[4]]
      promise = Promise.resolve(config);
      while (chain.length) {
       // (depressing, Rejected) One group of two each time
       // shift: Removes the first element from the array and returns the value of that element. This method changes the length of the array.
        // Each time the promise is reassigned
        promise = promise.then(chain.shift(), chain.shift());
      }
      
      return promise;
    }
  
    // Here is the synchronization logic
    var newConfig = config;
    // Request interceptors to go one by one
    while (requestInterceptorChain.length) {
      var onFulfilled = requestInterceptorChain.shift();
      var onRejected = requestInterceptorChain.shift();
      try {
        // Get the latest config every time
        newConfig = onFulfilled(newConfig);
      } catch (error) {
        onRejected(error);
        break; }}// The microtask will not be created prematurely by this point
    // This solves the problem of premature creation of microtasks, the current macro task being too long, or an asynchronous task in a request interceptor blocking 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

Interceptor application

  // Add request interceptor
  axios.interceptors.request.use(function (config) {
      / / send the token
      try {
        let token = localStorage.getItem('token');
        config.headers.authorization = token;
      } catch(e){}
      return config;
    }, function (error) {
      return Promise.reject(error);
    });
  
  // Add a response interceptor
  axios.interceptors.response.use(function (response) {
      console.log('Request Log', response);
      return response;
    }, function (error) {
      console.log('Request Log', response);
      alert('Wrong');
      return Promise.reject(error);
    });
Copy the code

(5) Adapter

In the browser, we use the API provided by XMLHttpRequest to send the request. In node.js, we need to use the API provided by the HTTP module to send the request. The underlying API structure, including the formatting of the response data, is also inconsistent. Axios, in order to address this discrepancy and allow users to use the same API in different environments, uses adaptation mode.

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;
 }
 ​
 ​
 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

./ Adapters /xhr.js is a wrapper for the native Ajax XMLHttpRequest object.

./ Adapters/HTTP. js encapsulates the Node HTTP module and handles it accordingly for HTTPS.

(6) Write at the end

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

Content Reference (thanks) :

Lot – Axios warehouse

The most complete and detailed reading of the Axios source code – this is enough

This article continues to update ~ welcome to follow my nuggets and Github ~