After reading the Source code of Axios (part 1) in the previous chapter, you know that Axios is essentially a function that can either call axios() directly or use axios.get() as an object. Next to the network request source (Request) and the core interceptor (Interceptor) to interpret.

1. Call AXIos to send the request

A typical project that introduces the Axios library and uses either Axios (URL, config) or axios.get(URL, config) is essentially calling the Request method on the Axios constructor.

This method mainly does the following:

  1. The config configuration is combined with the default defaults configuration for different AXIos calls.

  2. Use a Promise’s chained stream to connect interceptors and requests in sequence;

  3. Finally, return the Promise object;

1.1. Merge configuration items

// File location: lib/core/ axios.js
/** * Send request method *@param {Object} Config merges the configuration on the defaults property as the configuration */ on the request
Axios.prototype.request = function request(config) {
  /* Start the first part */
  // This judgment applies to axios(' interface address URL '[,config])
  if (typeof config === 'string') {
    // Execute axios() to set the second parameter to config
    config = arguments[1) | | {};// The first parameter is the URL
    config.url = arguments[0];
  } else {
    config = config || {};
  }
  // Merge attributes
  config = mergeConfig(this.defaults, config);

  if (config.method) {
    config.method = config.method.toLowerCase();
  } else if (this.defaults.method) {
    config.method = this.defaults.method.toLowerCase();
  } else {
    // Default get request
    config.method = 'get';
  }
  /* End of part one */. };Copy the code

The main action here is to merge the configuration defaults properties that are initialized with the config that is passed in when the developer calls AXIos. Typeof Config === ‘string’ is used because the user can call either axios(‘url’) or axios({url: ‘XXX ‘}).

1.2. Request and interceptor connection

Moving on to the REQUEST method on the Axios constructor, once the configuration processing is merged, the interceptors are processed, and there is an array (request interceptors, Ajax request interceptors, response interceptors) that is sequentially linked by a stream of calls to the PROMISE.

Axios.prototype.request = function request(config) {.../* The second step starts */
  // The chain array stores requests and interceptors
  var chain = [dispatchRequest, undefined];
  var promise = Promise.resolve(config);
  // If your code calls the use method in interceptors, add the interceptor function to the handles array.
  }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
  // This is why the interceptor is executed before the actual request is sent, because the chain array will be executed in order
  // The chain array structure is [request intercept success callback, request intercept failure callback, request function, undefined, response intercept success function, response intercept failure function]
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });

  // Put the use response interceptor at the end of the chain array, and then use promise to execute in the order of the chain array
  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });

  // Use the length of the chain array to control each THEN operation
  / / here is similar to the Promise. Resolve ({type: "get"}). Then (res = > {}). Then (res = > {}). Then (res = > {})... How many then depends on the length of the array /2, because the array contains success and failure callbacks.
  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }
  /* Step 2 ends */
  
};

Copy the code

The chain array is used to hold the request interceptor, ajax request, response interceptor related success and failure functions, request interceptor related functions will be inserted at the top of the chain array, response interceptor. If there is no interceptor, then the default is [dispatchRequest, undefined]. At this point, only the request will be sent, and no interception will be done.

Here’s a simple example to illustrate the above code for the reader’s convenience:

// The interceptor is invoked separately for two requests
let chain = [
  function () {console.log('Request interceptor 1')},
  function () {console.log('Send request 1')},
  undefined.function () {console.log('Response interceptor 1')}];let chain1 = [
  function () {console.log('Request Interceptor 2')},
  function () {console.log('Send request 2')},
  undefined.function () {console.log('Response interceptor 2')}];let p = Promise.resolve(1);
let p1 = Promise.resolve(2);
while(chain.length) {
  p = p.then(chain.shift());
}
while(chain1.length) {
  p1 = p1.then(chain1.shift());
}
/** Result output: Request interceptor 1 Request interceptor 2 Send request 1 Send request 2 Response interceptor 1 Response interceptor 2 */
Copy the code

The above example is essentially the same way the Axios source handles interceptors and requests, with promise’s chain of calls controlling the flow. In this example, we can see that the request interceptor function is always executed before the request is sent, and the response interceptor function is always executed after the request is sent, consistent with our usual use. The neat thing about this is that while is using the length of the chain array, and each execution is deleting the first element of the chain array, and the shift method returns the deleted element, passing in then.

1.3. Send an HTTP request

lib/core/dispatchRequest.js

function dispatchRequest(config) {
  / / determine if there is a call CancelToken. Cancel () method, the corresponding reason to the output
  throwIfCancellationRequested(config);

  // Check whether the request header exists
  config.headers = config.headers || {};

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

  // Flatters the request header object
  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]; });// Request method
  // The HTTP request will be sent according to the browser or Node environment
  var adapter = config.adapter || defaults.adapter;

  return adapter(config).then(function onAdapterResolution(response) {
    // There is also a judgment on cancellation
    throwIfCancellationRequested(config);

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

    return response;
  }, function onAdapterRejection(reason) {
    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

The different environment (browser or Node) is determined and the HTTP request is made using the appropriate method. In addition, the CancelToken configuration item is determined before the request is made. If the code calls the cancel method and passes in the reason, the subsequent operation will be interrupted. The code that actually sends HTTP network requests is in lib/ Adapters /xhr.js, where it is encapsulated with a Promise.

2. To summarize

  1. axiosMultiple design patterns are used inAxiosThe constructor uses the factory pattern, tool methodforEachUsing the iterator and adapter patterns;
  2. Interceptor processing is usedPromiseTo control the request interceptor,httpThe process of execution of the request and response interceptor function;
  3. axiosCallback functions have specific function names, which are important for code readability and understanding, and without a function name, anonymous functions will not show meaningful function names on the stack trace, making debugging difficult. This is a good coding habit.

If you see something wrong or could be improved, feel free to comment. If you think it’s good or helpful, please like, comment, retweet and share. Thank you