axios source code parsing (middle) code structure

Keywords: Javascript axios github JSON Attribute

The latest version of axios is v0.19.0. In this section, we will analyze its implementation source code. First, we get its source code through gitHub address, address: https://github.com/axios/axios/tree/v0.19.0

After downloading, you can see the directory structure of axios. There is an index.js file in the home directory. The file is relatively simple. The contents are as follows:

This is to introduce the. / lib/axios module. The contents of the Lib directory are as follows:

The general document is as follows:

index.js; entry file
_lib; code home directory
_helpers; Some auxiliary functions are defined
_adapters; encapsulation of requests in native ajax and node environments
_cancel; Unencapsulate some of the requests
_core; request dispatch, interceptor management, data conversion, etc.
axios.js; it's also an entry document.
default.js; default configuration file
Utls.js; Tool functions

Writby: Desert QQ:22969969

./lib/axios should also be an entry document. The main branches are as follows:

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

/*slightly*/

function createInstance(defaultConfig) {                        //Create a Axios The example parameter is:Axios Default configuration
  var context = new Axios(defaultConfig);                //Create A. / lib/core/Axios object as context
  var instance = bind(Axios.prototype.request, context);       //Create an instance attribute with a value of the return value of the bind() function

  // Copy axios.prototype to instance
  utils.extend(instance, Axios.prototype, context);          //take Axios.prototype Upper method(delete,get,head,options,post,put,patch,request)extend reach instans Pass on bind Binding

  // Copy context to instance
  utils.extend(instance, context);                    //take context Two of them defaults and interceptors Attributes are saved to utils Above, both are objects, so that we can pass through axios.defaults Modify configuration information by axios.interceptors To set up the interceptor

  return instance;                             //Return instance method
}

// Create the default instance to be exported
var axios = createInstance(defaults);                            //Create a default instance as output

/*slightly*/
module.exports = axios;                                            //Exported symbols

// Allow use of default import syntax in TypeScript
module.exports.default = axios;                                    //Default export symbol

createInstance creates an object instance of. / lib/core/Axios, saves it in the context of the local variable, then calls the bind function to save the return value in the instance (which is the symbol we call when we call axios() to execute the ajax request). Bid () is an auxiliary function, as follows:

module.exports = function bind(fn, thisArg) {        //with thisArg For context, execute fn function
  return function wrap() {
    var args = new Array(arguments.length);                //take arguments Save in sequence to args inside
    for (var i = 0; i < args.length; i++) {
      args[i] = arguments[i];
    }
    return fn.apply(thisArg, args);                        //implement fn Function, parameter is thisArg For context, args As parameter
  };
};

This function is a realization of a higher order function. It executes parameter 1 with parameter 2 as the context, that is, executing Axios.prototype.request function under the context of context, and Axios.prototype.request is the entry of all asynchronous requests.

Let's look at the implementation of Axios.prototype.request, as follows:

Axios.prototype.request = function request(config) {            //Send a request,Also ajax Request entry
  /*eslint no-param-reassign:0*/
  // Allow for axios('example/url'[, config]) a la fetch API
  if (typeof config === 'string') {                                   //If config Object is a string.  ;for example:axios('/api/1.php').then(function(){},function(){})
    config = arguments[1] || {};                                        //Convert it to an object
    config.url = arguments[0];
  } else {
    config = config || {};
  }

  config = mergeConfig(this.defaults, config);                          //Merge defaults
  config.method = config.method ? config.method.toLowerCase() : 'get';  //ajax Method, for example:get,Here is the conversion to lowercase

  // Hook up interceptors middleware
  var chain = [dispatchRequest, undefined];                             //This is sending ajax Asynchronous alignment of
  var promise = Promise.resolve(config);                                //take config Convert to Promise object

  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {      //Logic of Request Interceptor(Introduction to the next section)
    chain.unshift(interceptor.fulfilled, interceptor.rejected);                               
  });

  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {       //Logic of Response Interception(Next section)
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });

  while (chain.length) {                                                //If chain.length existence
    promise = promise.then(chain.shift(), chain.shift());                 //Then execute promise.then(),Execute here dispatchRequest Function, which makes up an asynchronous queue
  }

  return promise;                                                     //Last return promise object
};

Here's a while(chain.length) {} traversal loop that's hard to understand. This design idea is novel. If you understand the whole axios execution process, you can understand it. The interceptor is also implemented here. It traverses the chain array and executes the first two elements as promise (). the first two elements in turn as parameters 1 and 2 of promise().then the queue before promise is executed and then the queue after promise is executed. The default is [dispatchRequest,undefined], that is, dispatchRequest is executed first, and interception is executed before dispatchRequest if a request interceptor is added. Similarly, if there is a response interceptor, the logic in the response interceptor will be executed after dispatchRequest.

The dispatchRequest logic is as follows:

module.exports = function dispatchRequest(config) {                //To dispatch a request to the server, use config Configuration inside
  throwIfCancellationRequested(config);

  // Support baseURL config
  if (config.baseURL && !isAbsoluteURL(config.url)) {                //If config.baseURL Existence, and config.url Not absolute URL(with http://Beginning)
    config.url = combineURLs(config.baseURL, config.url);                //Then call combineURLs take config.baseURL Pieced together config.url In the front, we set it up in the project. baseURL="api/"It's here that we deal with it.
  }

  // Ensure headers exist
  config.headers = config.headers || {};                            //ensure headers existence

  // Transform request data
  config.data = transformData(                                        //Modifying the request data will call the default configuration transformRequest Processing
    config.data,
    config.headers,
    config.transformRequest
  );

  // Flatten headers
  config.headers = utils.merge(                                     //Merge request headers into an array
    config.headers.common || {},
    config.headers[config.method] || {},
    config.headers || {}
  );

  utils.forEach(                                                     //Delete again config.headers Li delete,get,head,post,put,patch,common Request header
    ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
    function cleanHeaderConfig(method) {
      delete config.headers[method];
    }
  );
  //The request header has been set up to execute here.
  var adapter = config.adapter || defaults.adapter;                 //Get the default configuration adapter,That's encapsulated. ajax Requester

  return adapter(config).then(function onAdapterResolution(response) {    //implement adapter()Will send ajax Request.,then()The first parameter corrects the returned value
    throwIfCancellationRequested(config);

    // Transform response data
    response.data = transformData(                                         //Call the default configuration transformResponse Processing the returned data
      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);
  });
};

Finally, the function corresponding to the adapter attribute in the default configuration will be executed. Let's take a look at it, as follows:

function getDefaultAdapter() {                //Get the default adapter,Namely Ajax Sender bar
  var adapter;
  // Only Node.JS has a process variable that is of [[Class]] process
  if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {        //For browsers, use XHR adapter
    // For node use HTTP adapter
    adapter = require('./adapters/http');
  } else if (typeof XMLHttpRequest !== 'undefined') {                                                            //about node For the environment, use HTTP adapter
    // For browsers use XHR adapter
    adapter = require('./adapters/xhr');
  }
  return adapter;
}

var defaults = {
  adapter: getDefaultAdapter(),            //Adapter
  /*slightly*/
}

/ adapters/http is the implementation of the final ajax request. The main logic is as follows:

module.exports = function xhrAdapter(config) {                            //Send out XMLHTtpRequest()Request, etc.
  return new Promise(function dispatchXhrRequest(resolve, reject) {
    var requestData = config.data;
    var requestHeaders = config.headers;

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

    var request = new XMLHttpRequest();

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

    request.open(config.method.toUpperCase(), buildURL(config.url, config.params, config.paramsSerializer), true);        //Initialization HTTP Asynchronous request invocation buildURL Obtain URL address

    // Set the request timeout in MS
    request.timeout = config.timeout;                                                    //Setting timeout time

    // Listen for ready state
    request.onreadystatechange = function handleLoad() {                                //binding onreadystatechange Event
      if (!request || request.readyState !== 4) {                                            //If HTTP The response has not been received yet.
        return;                                                                                    //It returns directly without processing.
      }

      // The request errored out and we didn't get a response, this will be
      // handled by onerror instead
      // With one exception: request that using file: protocol, most browsers
      // will return status as 0 even though it's a successful request
      if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {    //Request error, no response logic if request.responseURL Not with file:Beginning and request.status=0,Return directly
        return;
      }

      // Prepare the response
      var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null;        //Resolve the response header and call it parseHeaders Convert it to an object and save it to responseHeaders inside
      var responseData = !config.responseType || config.responseType === 'text' ? request.responseText : request.response;    //If not set config.responseType Or set it up responseType.responseType And equal to text,Direct acquisition request.responseText,Otherwise get request.response
      var response = {                                            //The patchwork of returned data, as mentioned in the previous article axios Return after request promise object
        data: responseData,                                            //Received data
        status: request.status,                                        //state ie The browser replaces port 204 with port 1223. See:https://github.com/axios/axios/issues/201
        statusText: request.statusText,                                //Response Header Status Text
        headers: responseHeaders,                                    //Head information
        config: config,                                                //configuration information
        request: request                                             //Corresponding XmlHttpRequest object
      };

      settle(resolve, reject, response);                        //call settle Function to judge, yes resolve perhaps reject

      // Clean up request
      request = null;
    };

    /*Abbreviated, mainly for error, timeout, some processing*/

    // Add headers to the request
    if ('setRequestHeader' in request) {                        //If request In existence setRequestHeader
      utils.forEach(requestHeaders, function setRequestHeader(val, key) {                //ergodic requestHeaders
        if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') {    //If key Be equal to content-type And no data was sent.
          // Remove Content-Type if data is undefined 
          delete requestHeaders[key];                                                             //Delete content-type This request header      ;Only when data is sent content-type Is it useful?
        } else {
          // Otherwise add header to the request
          request.setRequestHeader(key, val);                                                 //Otherwise, set the request header
        }
      });
    }

    // Add withCredentials to request if needed
    if (config.withCredentials) {                                 //Use credentials if cross-domain requests are set
      request.withCredentials = true;                                 //Set up request.withCredentials by true
    }

    // Add responseType to request if needed
    if (config.responseType) {                                     //If the data type of the server response is set, the default is json
      try {
        request.responseType = config.responseType;
      } catch (e) {
        // Expected DOMException thrown by browsers not compatible XMLHttpRequest Level 2.
        // But, this can be suppressed for 'json' type as it can be parsed by default 'transformResponse' function.
        if (config.responseType !== 'json') {
          throw e;
        }
      }
    }

    // Handle progress if needed
    if (typeof config.onDownloadProgress === 'function') {                     //If download processing progress events are set up
      request.addEventListener('progress', config.onDownloadProgress);
    }

    // Not all browsers support upload events
    if (typeof config.onUploadProgress === 'function' && request.upload) {     //If upload processing progress events are set
      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);
        // Clean up request
        request = null;
      });
    }

    if (requestData === undefined) {                                     //correct requestData,If so undefined,Is amended to null
      requestData = null;
    }

    // Send the request
    request.send(requestData);                                             //send data
  });
};

That's the native ajax request, and the main logic is commented on, so the whole process runs out.

For convenient methods, such as axios.get() and axios.post(), it is an encapsulation of Axios.prototype.request. The implementation code is as follows:

utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {    //Definition delete,get,head,options Method
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, config) {
    return this.request(utils.merge(config || {}, {                                             //call utils.merge Merge parameters into an object, and then call request()Method
      method: method,
      url: url
    }));
  };
});

utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {              //Definition post,put,patch Method
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, data, config) {                                       //call utils.merge Merge parameters into an object, and then call request()Method
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url,
      data: data                                                                                //post,put and patch than get Waiting for more requests data,The others are the same.
    }));
  };
});

OK, get it done.

Posted by SystemWisdom on Wed, 09 Oct 2019 17:03:37 -0700