An overview of the

The axios we introduced is actually axios’s prototypical method Request (function object), whose execution context is an instance object of AXIos.

  • The instance is extendedAxiosMethods on the constructor prototype, as well as public propertiesdefaults,interceptors.
  • Exposed theaxios.AxiosTo allow other classes to inherit.
  • providescreateMethod is used to create a factory function for a new instance.
  • providesCancel,CancelTokenisCancelUsed to interrupt a request.
  • providesall,spreadMethod is used to send multiple requests simultaneously.

createInstance()

// lib/axios.js
/** * Create an Axios instance *@param {Object} Default configuration for defaultConfig instance *@return {Axios} Return a new Axios instance */
function createInstance(defaultConfig){
  // An axios instance is created primarily as a context for axios execution, and the following extended methods will bind this instance to this
  // Why do you do that?
  // What's the difference if you just create an instance instead of doing that?
  var context = new Axios(defaultConfig)
  
  // Export the request method as an instance to axios.
  var instance = bind(Axios.prototype.request, context)
  
  // Extend the methods on the Axios prototype to the instance and bind this of the function to the context instance created above.
  Utils.extend(instance, Axios.prototype, context)
  
  // Extend the Axios constructor's public attributes to the instance
  Utils.extend(instance, context)
  
  return instance
}

var axios = createInstance(defaults);
module.exports = axios;

// Allow use of default import syntax in TypeScript
module.exports.default = axios;
Copy the code
// lib/helpers/bind.js

function bind(fn, thisArg){
  return function warp(){
    var args = new Array(arguments.length);
    for(var i = 0; i < args.length; i++){
    	args[i] = arguments[i]
    }
    return fn.apply(thisArg, args)
  }
}
Copy the code
// lib/utils.js

/**
 * Extends object a by mutably adding to it the properties of object b.
 * 
 * @param {Object} a The object to be extended
 * @param {Object} b The object to copy properties from
 * @param {Object} thisArg The object to bind function to
 * @return {Object} The resulting value of object a
 */
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
    }
  })
}

/**
 * Iterate over an Array or an Object invoking a function for each item.
 *
 * If `obj` is an Array callback will be called passing
 * the value, index, and complete array for each item.
 *
 * If 'obj' is an Object callback will be called passing
 * the value, key, and complete object for each property.
 *
 * @param {Object|Array} obj The object to iterate
 * @param {Function} fn The callback to invoke for each item
 */
function forEach(obj, fn){
  if(obj === null && typeof obj === 'undefined') {return 
  }
  
  if(typeofobj ! = ='object'){
  	obj = [obj]
  }
  
  if(isArray(obj)){
  	for(var i = 0, l = obj.length; i < l; i++){
    	fn.call(null, obj[i], i, obj)
    }
  }else{
  	for(var key in obj){
    	if(Object.prototype.hasOwnProperty.call(obj, key)){
      	fn.call(null, obj[key], key, obj)
      }
    }
  }

}
Copy the code

Axios constructor

Public attribute

  • defaultsUsed to save the default configuration items of the Axios library
  • interceptorscontainsrequestresponseTwo request interceptors.

Prototype method

  • request: The main method used to send a request.
  • getUri: Get the requestUri.
  • delete ,gethead,options: alias of the request method, which is actually calledrequestMethods.
  • post,put,patch: alias of the request method, which is actually calledrequestMethods.
// lib/core/Axios.js

function Axios(instanceConfig){
  this.defaults = instanceConfig;
  this.insterceptors = {
  	requset: new InterceptorManager(),
    reqponse: new InterceptorManager()
  }
}

Axios.prototype.request = function request(config){
  
  // Either axios(config) or axios(url[, config]) can be called
  // Get the requested URL
  if(typeof config === 'string'){
    config = arguments[1) | | {}; config.url =arguments[0];
  }else{
    config = config || {}
  }
  // Merge request configuration information
  config = mergeConfig(this.defaults, config)
  
  // Get the request method by default
  if(config.method){
  	config.method = config.method.toLowerCase()
  }else if{this.defaults.method}{
  	config.method = this.defaults.method.toLowerCase()
  }else{
  	config.method = 'get'
  }
  
  // The connection interceptor middleware chain is used to register the promise's callback chain
  var chain = [dispatchRequest, undefined];
  // Generate a successful Promise instance and pass in the request configuration information
  var promise = Promise.resolve(config);
  
  // Put the request interceptor at the top of the callback chain
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor){
  	chain.unshift(interceptor.fulfilled, interceptor.rejected)
  })
   // Put the response interceptor at the end of the callback chain
  this.interceptors.response.forEach(function pushRequestInterceptors(interceptors){
  	chain.push(interceptor.fulfilled, interceptor.rejected)
  })
  
  // Bind the callback functions to the Promise callback chain in turn. If an error is reported, the exception will be dropped to the next layer according to the Promise exception pass-through principle
  // promise.then(request interceptor).then(send request).then(response interceptor)
  while(chain.length){
  	promise = promise.then(chain.shift(), chain.shift())
  }
  
  return promise
}
// 
Axios.prototype.getUri = function getUri(config){
	config = mergeConfig(this.defaults, config)
  return buildURL(congig.url, config.params, config.paramsSerializer).replace(/ ^ \? /.' ')}// Add a request method alias without data to the prototype
utils.forEach(['get'.'head'.'options'.'delete'].function forEachMethodNoData(method){
	Axios.prototype[method] = function(url, config){
  	return this.request(mergeConfig(config || {}, {
    	method: method,
      url: url
    }))
  }
})
// Add a request method alias containing data to the prototype
utils.forEach(['post'.'put'.'petch'].function forEachMethodWithData(method){
	Axios.prototype[method] = function(url, data, config){
  	return this.request(mergeConfig(config || {}, {
    	method,
      url,
      data
    }))
  }
})
module.export = Axios
Copy the code

The interceptor

Axios provides us with request interceptors and response interceptors. Registered interceptors are stored on the request and Response properties of the public interceptors. Managed by an interceptorManager instance. Let’s look at how the InterceptorManager constructor is implemented.

  • By calling theuseMethod stores interceptors on the stack
  • By calling theejectMethod to remove interceptors from the stack
  • By calling theforEachMethod iterates through interceptors on the stack and registers interceptors.
function InterceptorManager(){
	this.handlers = []
}

/** * Add a new interceptor to the stack * Add a new interceptor to the Handlers *@param {Function} fulfilled The function to handle `then` for a `Promise`
 * @param {Function} rejected The function to handle `reject` for a `Promise`
 *
 * @return {Number} An ID used to remove interceptor later * Returns the ID of the interceptor used when removing it
InterceptorManager.prototype.use = function use(fulfilled, rejected){
	this.handlers.push({
  	fulfilled,
    rejected
  })
  return this.handler.length - 1
}
/**
 * Remove an interceptor from the stack
 *
 * @param {Number} id The ID that was returned by `use`
 */
InterceptorManager.prototype.eject = function eject(id){
	if(this.handlers[id]){
  	this.handlers[id] = null}}/** * Iterate over all the registered interceptors * This method is particularly useful for skipping over any * interceptors that may have become 'null' calling `eject`. * *@param {Function} fn The function to call for each interceptor
 */
InterceptorManager.prototype.forEach = function forEach(fn){
	utils.forEach(this.handlers, function forEachHandler(h){
  	if(h ! = =null){
    	fn(h)
    }
  })
}
Copy the code

dispatchRequest()

This method is the main method used in Request () to send requests to the server. The return result is also a promise.

function transformData(data, headers, fns){
	utils.forEach(fns, function(fn){
  	data = fn(data, headers)
  })
  return data
}

function dispatchRequest(config){
	// Used to allow the user to manually cancel the request
  throwIfCancellationRequested(config)
  
  // Make sure the request header exists
  config.headers = config.headers || {};
  // Request data conversion
  Config. TransformRequest is an array
  // This contains the default request data conversion method as well as user-defined additions (if any).
  config.data = transformData(
  	config.data,
    config.headers,
    config.transformRequest
  )
  // The request header is flat
  confif.headers = utils.merge(
  	config.headers.common || {},
    config.headers[config.method] || {},
    config.headers
  )
  
  utils.forEach(
    ['delete'.'get'.'head'.'post'.'put'.'patch'.'common'].function clearHeaderConfig(){
            delete config.headers[method]
    }
  )
  
  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){
    	if(! isCancel(reason)){ throwIfCancellationRequestd(config)// Convert the response data
        reason.response = transformData(
        	reason.response.data,
          reason.response.headers,
          config.transformResponse
        )
      }
      return Promise.reject(reason)
    }
  )
}
Copy the code

Adapter Request adapter

Axios works with Node.js as well as browsers. So two solutions are also implemented in the method of request.

  • Browser usageXMLHttpRequest
  • Node. JshttphttpsThe middleware
function getDefaultAdapter() {
  var adapter;
  if (typeofXMLHttpRequest ! = ='undefined') {
    // For browsers use XHR adapter
    adapter = require('./adapters/xhr');
  } else if (typeofprocess ! = ='undefined' && Object.prototype.toString.call(process) === '[object process]') {
    // For node use HTTP adapter
    adapter = require('./adapters/http');
  }
  return adapter;
}
Copy the code

Let’s take a look at how XMLHttpRequest is handled.

// lib/adapters/xhr

module.exports = function xhrAdapter(config){
return new Promise(function dispatchXhrRequest(resolve, reject){
    var requestData = config.data;
    var requestHeaders = config.headers;
    
    // FormData
    if(utils.isFormData(requestData)){
    	delete requestHeaders['Content-Type']  // Let the browser set it up
    }
    
    if(utils.isBlob(requestData) || utils.isFile(requestData) && requestData.type){
    	delete requestHeaders['Content-Type']  // Let the browser set it up
    }
    
    // Create an asynchronous request
    var request = new XMLHttpRequest();
    HTTP basic authentication
    if(config.auth){
      var username = config.auth.username || ' ';
      var password = unescape(encodeURIComponent(config.auth.password)) || ' ';
      requestHeaders.Authorization = 'Basic' + btoa(username + ':' + password);
    }
    // Concatenate the complete requested address, if it is an absolute path, directly return.
    var fullPath = buildFullPath(config.baseURL, config.url)
    request.open(config.method.toUpperCase(), 
                 buildURL(fullPath, config.params, config.paramsSerializer), 
                 true
                );
    // Set the request timeout in MS
    request.timeout = config.timeout;
    
    request.onreadystatechange = function handleLoad(){
      // 0 proxy is created, but open() has not yet been called
      // 1 open() has been called
      // 2 send() has been called, and the header and state are available
      // 3 The responseText property in the download already contains some data
      // 4 Download complete
      // Request status code 4 indicates that the request has completed. Download operation has completed.
     if(! request || request.readyState ! = =4) {return;
      }
      // If the request goes wrong and we don't get a response, it will be handled by onError
      With one exception, if the request uses the File: protocol, most browsers will return a status code of 0, even though the request was successful.
      if(
        request.status === 0 && 
        !(request.responseURL && request.responseURL.indexof('file:') = = =0)
      ){
      	return;
      }
      
      // Prepare response data
      // Get the response header
      var responseHeaders = 'getAllResponseHeaders' in request ? getAllResponseHeaders() : null
      // If the response type is text, take responseText as the result
      varresponseData = ! config.responseType || config.reponseType ==='text' ? 
          								request.responseText : request.response;
      
      var response = {
      	data: responseData,
        status: request.status,
        statusText: request.statusText,
        headers: responseHeaders,
        config: config,
        request: request
      }
      // Determine success or failure according to the response code. Users can customize vaildateStatus
      settle(resolve, reject, response)
      // Clear the request
      request = null
    }
    // Handle browser cancel requests (not manually)
    request.onabort = function handleAbort(){
    	if(! request) {return;
        }

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

      // Clean up request
      request = null;
    }
    
    // Handle network errors when the network speed is slow
    request.onerror = function handleError(){
      // Real errors are hidden from us by the browser
      // onerror should only fire if it's a network error
      reject(createError('Network Error', config, null, request));

      // Clean up request
      request = null;
    }
    
    // Handle timeout
    request.ontimeout = function handleTimeout() {
      var timeoutErrorMessage = 'timeout of ' + config.timeout + 'ms exceeded';
      if (config.timeoutErrorMessage) {
        timeoutErrorMessage = config.timeoutErrorMessage;
      }
      reject(createError(timeoutErrorMessage, config, 'ECONNABORTED',
        request));

      // Clean up request
      request = null;
    };
    
    // Add xsrf header
    // This is only done if running in a standard browser environment.
    // Specifically not if we're in a web worker, or react-native.
    if (utils.isStandardBrowserEnv()) {
      // Add xsrf header
      var xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath)) 
                    && config.xsrfCookieName 
                    ? cookies.read(config.xsrfCookieName)
                    : undefined;

      if(xsrfValue) { requestHeaders[config.xsrfHeaderName] = xsrfValue; }}// Add headers to the request
    if ('setRequestHeader' in request) {
      utils.forEach(requestHeaders, function setRequestHeader(val, key) {
        if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') {
          // Remove Content-Type if data is undefined
          delete requestHeaders[key];
        } else {
          // Otherwise add header to the requestrequest.setRequestHeader(key, val); }}); }// Add withCredentials to request if needed
    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 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') {
          throwe; }}}// Handle progress if needed
    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 cancel events
      config.cancelToken.promise.then(function onCanceled(cancel){
      	if(! request){return
        }
        request.abort();
        reject(cancel);
        request = null})}if(! requestData) { requestData =null;
    }

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

Let’s take a look at the success and failure of custom status codes

function settle(resolve, reject, response){
  var validateStatus = response.config.validateStatus;
  if(! response.status || ! validateStatus || validataStatus(response.status)){ resolve(response) }else{
  	reject(createError(
            'Request failed with status code ' + response.status,
              response.config,
              null,
              response.request,
              response
            )
        )
  }
}
// The default validateStatus is
function validateStatus(status){
	return status >= 200 && status < 300
}
Copy the code