The previous three articles on axios part of the source code analysis, now we in from the process of combing through. There will be some repetition with the previous content, I may briefly cover, you can go to the previous content.

Here, I have prepared a simple code:

import axios from 'axios'

axios.default.baseUrl = 'www.xxx.com'
axios.default.timeout = 60000

axios.interceptors.request.use(config= > {
  if (config.token) {
    config.header['token'] = config.token
    delete config.token
  }
}, err => {
  console.log(err)
  return Promise.reject(error)
})

axios.interceptors.response.use(res= > {
  console.log('res: ', res)
  if (res.status === 204) return null
  return res.data
}, err => {
  console.log('err: ', err)
  switch (err.response.status) {
    // do something
  }
  return Promise.reject(error)
})

axios.get('/user', {
  params: {
    ID: 12345
  }
}).then(function (response) {
  console.log(response);
}).catch(function (error) {
  console.log(error);
})
Copy the code

This code is what we would normally do if we simply sent a request using AXIos. Introduce AXIos, then set the request prefix and timeout, then set the interceptor, and finally send the request to handle the request response.

axios.default

Before sending a request, we can do the common configuration for all requests. If we use the exported instance of axios() directly, we can set it via axios.default (the source code attaches the default of the instance of axios() to axios()); If you create an axios instance with axios.create(), the source code first merges the passed configuration with the default configuration, and then we can set the common configuration using the default attribute of the instance itself.

Can a request be successfully sent by creating an instance directly through axios.axios? This is actually a difficult operation to implement because it involves the request configuration problem, which can be understood by analyzing the source code’s public configuration defaults.

In the source code, there is a relatively complete public configuration stored in lib/defaults.js. It can be divided into adapters, request converters, response converters, timeout configurations, cache configurations, request header configurations, and status code validators.

The adapter

The adapter is a key configuration. The source code implementation uses a function to determine whether the environment is Node or browser, and then returns either an xHLHttprequest-based adapter or an HTTP based adapter.

/ / defaults the source code
// Access the adapter
// If the XMLHttpRequest class exists in the browser environment, use the XMLHttpRequest class to implement
// In the Node environment, HTTP is used
function getDefaultAdapter() {
  var adapter;
  if (typeofXMLHttpRequest ! = ='undefined') { // Determine whether the XMLHttpRequest class exists
    // 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;
}

var defaults = {
  adapter: getDefaultAdapter(),

  // ...
}
Copy the code

The adapter is called in the dispatchRequest() method:

/ / dispatchRequest source code
module.exports = function dispatchRequest(config) {
  // ...

  // Return a Promise after the adapter then, which is the processing after the request ends
  return adapter(config).then(/ *... * /)

  // ...
}
Copy the code

Request converter and response converter

These two converters are used before the request and after the response, respectively, to process the data format. Both converters are an array within the source code and have default handlers. This allows you to override or add multiple handlers externally.

The default function of the request converter is mainly to process the type of the requested data. The internal function is to determine the type of the data, and then to process the instances of ArrayBufferView, URLSearchParams and Object.

/ / defaults the source code
var defaults = {
  // ...

  // 'transformRequest' allows request data to be modified before being sent to the server
  // Only for 'PUT', 'POST', and 'PATCH' request methods
  // The function in the following array must return a string, either an ArrayBuffer, or a Stream
  // transformRequest The transformResponse is handled in the transformData function of the dispatchRequest function
  transformRequest: [function transformRequest(data, headers) {
    normalizeHeaderName(headers, 'Accept'); // Replace the Accpet attribute
    normalizeHeaderName(headers, 'Content-Type'); // Replace the content-type attribute
    /** ** ** /
    if (utils.isFormData(data) ||
      utils.isArrayBuffer(data) ||
      utils.isBuffer(data) ||
      utils.isStream(data) ||
      utils.isFile(data) ||
      utils.isBlob(data)
    ) {
      return data;
    }
    if (utils.isArrayBufferView(data)) {
      return data.buffer;
    }
    if (utils.isURLSearchParams(data)) {
      setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded; charset=utf-8');
      return data.toString();
    }
    if (utils.isObject(data)) {
      setContentTypeIfUnset(headers, 'application/json; charset=utf-8');
      return JSON.stringify(data);
    }
    returndata; }].// ...
}
Copy the code

The default function for the response converter is simpler, converting String data to JSON format.

/ / defaults the source code
var defaults = {
  // ...

  // 'transformResponse' allows the response data to be modified before being passed to then/catch
  transformResponse: [function transformResponse(data) {
    /*eslint no-param-reassign:0*/
    if (typeof data === 'string') {
      try {
        data = JSON.parse(data);
      } catch (e) { /* Ignore */}}returndata; }].// ...
}
Copy the code

Request data and response data must be called before and after the request is invoked:

/ / dispatchRequest source code
module.exports = function dispatchRequest(config) {
  // ...

  // Transform request data
  // Process the requested data (request data and request headers)
  config.data = transformData(
    config.data,
    config.headers,
    config.transformRequest
  );

  // ...

  // Return a Promise after the adapter then, which is the processing after the request ends
  return adapter(config).then(function onAdapterResolution(response) {
    // ...

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

    return response;
  }, function onAdapterRejection(reason) {
    // ...
  })

  // ...
}
Copy the code

Cache configuration

There are only two common configurations for the cache in the source code, which are the configuration cookie name and the request header attribute name xsrfCookieName and xsrfHeaderName. XsrfCookieName indicates the cookie name of the token, and xsrfHeaderName indicates the header name of the token in the request headers.

These two configurations are used on requests. Before preparing the request, Axios will fetch the cached value of xsrfCookieName as key from the cookie, and then set the token value of xsrfHeaderName as key on the request header.

If xsrfCookieName is not set, this property is not set on the request header.

/ / XHR. Js source code
if (utils.isStandardBrowserEnv()) {
  var xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath)) && config.xsrfCookieName ?
    cookies.read(config.xsrfCookieName) :
    undefined;

  if(xsrfValue) { requestHeaders[config.xsrfHeaderName] = xsrfValue; }}Copy the code

Request Header configuration

/ / default. Js source code
defaults.headers = { // Set the default header
  common: {
    'Accept': 'application/json, text/plain, */*'}}; utils.forEach(['delete'.'get'.'head'].function forEachMethodNoData(method) {
  defaults.headers[method] = {};
});

utils.forEach(['post'.'put'.'patch'].function forEachMethodWithData(method) {
  defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE);
});
Copy the code

Status code validator

The purpose of the status code verifier is to determine whether the response is successful or failed. The default is the 2xx segment, but you can customize the validator function directly if your project needs it.

/ / default. Js source code
validateStatus: function validateStatus(status) { // Verify the status code successfully or failed
  return status >= 200 && status < 300;
}
Copy the code

Axios.default is not exported externally, but the author provides an additional instance of axios created through axios.create(). However, using axios. axios directly requires manual introduction of axios.default, which is cumbersome, so it is not recommended.

Send the request

So let’s go back to the code that I prepared. That code sets the request baseUrl and timeout, the prefix of the request address and the request timeout time, respectively, and then sets the request interceptor and response interceptor. Finally, a request is sent using axios.get.

Axios.get () internally calls axios.prototype. request, passing the argument to request along with the method specified as get.

/ / Axios. Js source code
utils.forEach(['delete'.'get'.'head'.'options'].function forEachMethodNoData(method) {
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url
    }));
  };
});
Copy the code

Axios.prototype.request is handled in two steps. The first step is to merge the configuration. The second step is to prepare an array with the dispatchRequest function and a NULL (the two elements are a pair) in the default array. We then insert request interceptor functions and corresponding interceptor functions into the header and tail of the array, respectively. Then create a new Promise instance and pass in the request configuration directly to resolve, iterating over the set of numbers, using the paired functions as two functions of the Promise instance’s THEN function.

The request configuration is then passed through the request interceptor, then the dispatchRequest function is called and passed in the request configuration returned by the interceptor, and finally the corresponding data is passed through the corresponding interceptor.

The dispatchRequest function does the configuration of the dispatchRequest, and then calls the adapter to send the request, and then receives the corresponding data, and then returns the collated data.

Axios.prototype.request has analyzed the source code in the first article and will not be explained in detail here. The source logic of dispatchRequest function is also relatively simple and will not be explained here.

The adapter

The adapter is a core part of AXIOS because it is used to make requests. Axios supports node and browser environments. In the previous, it was written that, in fact, after judging the environment, the corresponding request module was introduced. The Node environment uses the Node HTTP module, while the browser environment uses XMLHttpRequest. The two request modules handle much the same, so here’s a look at some of the source code for making a request using XMLHttpRequest.

The adapter function that makes the request using XMLHttpRequest is called an xhrAdapter, which essentially returns a Promise instance inside.

/ / XHR. Js source code
module.exports = function xhrAdapter(config) {
  return new Promise(function dispatchXhrRequest(resolve, reject) {
    // ...}}Copy the code

The dispatchXhrRequest function collates the request data, handles some special requests, and concatenates the complete request URL.

Xhr.js dispatchXhrRequest function promise internal source code
var requestData = config.data;
var requestHeaders = config.headers;

// Check whether data is FormData. If yes, delete content-type
if (utils.isFormData(requestData)) {
  delete requestHeaders['Content-Type']; // Let the browser set it
  // Delete content-type. The browser will set the content-type to multipart/form-data by default
}

// ...

// HTTP basic authentication
// Set authentication information if user authentication is required
// bTOA encodes user authentication information
if (config.auth) {
  var username = config.auth.username || ' ';
  var password = config.auth.password || ' ';
  requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password);
}

// Concatenate the request URL
var fullPath = buildFullPath(config.baseURL, config.url);

// ...

// Add xsrf header
// Check if it is a standard browser environment
if (utils.isStandardBrowserEnv()) {
  // When withCredentials are true, cookies are supported, or whether the requested address and the page address are in the same domain, and the xsrfCookieName key name is set
  // If yes, the cookie is read and attached to the request head
  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 there is a method to set the request header setRequestHeader, set the header part in config
// If the key is content-type and requestData is empty, content-type is not set
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 config.withCredentials has a value, set the withCredentials on the request instance
if(! utils.isUndefined(config.withCredentials)) { request.withCredentials = !! config.withCredentials; }// Add responseType to request if needed
// If config.responseType has a value, try setting it. If not, throw an error
/ / reference nguyen piece of http://www.ruanyifeng.com/blog/2012/09/xmlhttprequest_level_2.html
// This is done because older versions of XMLHttpRequest can only return a text type, not a return type.
// However, if the request return type is JSON, execution can continue
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; }}}// ...

// If requestData does not exist, set to null
if(! requestData) { requestData =null;
}
Copy the code

If cancelToken is set, then is set to execute cancelToken. Promise.

Xhr.js dispatchXhrRequest function promise internal source code
// If cancelToken is set, then is set
if (config.cancelToken) {
  // Handle cancellation
  config.cancelToken.promise.then(function onCanceled(cancel) {
    // If the request has been cleared, it is no longer processed
    if(! request) {return;
    }

    // Cancel the request, reject the Promise, and empty the request
    request.abort();
    reject(cancel);
    // Clean up request
    request = null;
  });
}
Copy the code

Then you create an XMLHttpRequest instance, issue the request, listen for the status of the request, and set up the listener for the request. On the request writing method, here do not do too much introduction, directly look at the source:

Xhr.js dispatchXhrRequest function promise internal source code
// ...

// Create an HTTP request
var request = new XMLHttpRequest();

// ...

// Initialize the request
request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);

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

request.onreadystatechange = function handleLoad() {
  // If the request is gone or readyState is not equal to 4, no processing is done
  if(! request || request.readyState ! = =4) {
    return;
  }

  // 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
  // request.responseURL - Returns the serialized URL of the response, or an empty string if the URL is empty.
  if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') = = =0)) {
    return;
  }

  // Prepare the response
  / / request. GetAllResponseHeaders - in the form of a string returns all use CRLF space response headers, if no response is received, it returns null.
  var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null;
  varresponseData = ! config.responseType || config.responseType ==='text' ? request.responseText : request.response;
  var response = { // Put the contents of the response together
    data: responseData,
    status: request.status,
    statusText: request.statusText,
    headers: responseHeaders,
    config: config,
    request: request
  };

  settle(resolve, reject, response);

  // Clean up request
  request = null;
};

// Handle browser request cancellation (as opposed to a manual cancellation)
// Listen for the request to cancel, reject the Promise, and pass in an Error instance of the cancellation request
request.onabort = function handleAbort() {
  if(! request) {return;
  }

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

  // Clean up request
  request = null;
};

// Handle low level network errors
// Listen for a failed request, reject the Promise, and pass in an Error instance that failed in response
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
// Listen timed out
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;
};

// ...

// Handle progress if needed
// If there is a declaration to download the process function, do the listening
if (typeof config.onDownloadProgress === 'function') {
  request.addEventListener('progress', config.onDownloadProgress);
}

// Not all browsers support upload events
// If there is a declaration of the upload process function, do the listening
if (typeof config.onUploadProgress === 'function' && request.upload) {
  request.upload.addEventListener('progress', config.onUploadProgress);
}

// ...

// Send the request
// After setting the request property parameters, send the request
request.send(requestData);

Copy the code

Above you can see, in the request. In the onreadystatechange, when get response, will settle method, the response content with Promise instance to reject to resolve. Internally, you’re handling the state of the response and determining whether the response is a success or failure.

// settle.js
module.exports = function settle(resolve, reject, response) {
  var validateStatus = response.config.validateStatus;
  // If there is no status code, or no check function, or if there is a check function and the check is true, go resolve
  if(! response.status || ! validateStatus || validateStatus(response.status)) { resolve(response); }else {
    // Otherwise, reject, create a request Error instance
    reject(createError(
      'Request failed with status code ' + response.status,
      response.config,
      null, response.request, response )); }};Copy the code

This determines whether the response to the request should go to the success or failure interceptor of the response interceptor. Then go through the entire AXIos request.

conclusion

The axios source code is not hard to read, and there is a lot to learn and accumulate. I will continue to read other source code when I have the opportunity to try to organize some source code analysis. I hope I can leave some useful accumulation behind.