1. Review Axios core functions

Take a look at Axios’s official documentation:

Axios is a promise-based HTTP Client for node.js and the browser. It is isomorphic (= it can run in the browser and nodejs with the same codebase). On the server-side it uses the native node.js http module, while on the client (browser) it uses XMLHttpRequests.

Axios is a Promise based HTTP library that can run in a browser and node.js environment, so the core function of an HTTP library is to construct and make HTTP requests. So let’s start by looking at how Axios implements a GET/POST request.

Second, read the source code

To reference the first sample code from the document:

const axios = require('axios'); // Make a request for a user with a given ID axios.get('/user? ID=12345') .then(function (response) { // handle success console.log(response); }) .catch(function (error) { // handle error console.log(error); }) .then(function () { // always executed });Copy the code

Through the above code we know:

  1. AxiosThe library is exposedaxiosThere’s one ongetMethods;
  2. usegetMethods can initiateHTTPRequests;
  3. getMethod returns apromiseObject;

Let’s read about these three points.

2.1 AXIos objects and GET methods

/lib/axios.js. Open that file to find the line of module.exports.default = axios, so the axios object in this file is the object used in the example code. Let’s see how this object is created.

Portal:./lib/axios.js

var Axios = require('./core/Axios');

/**
 * Create an instance of Axios
 *
 * @param {Object} defaultConfig The default config for the instance
 * @return {Axios} A new instance of Axios
 */
function createInstance(defaultConfig) {
  var context = new Axios(defaultConfig);
  var instance = bind(Axios.prototype.request, context);

  // Copy axios.prototype to instance
  utils.extend(instance, Axios.prototype, context);

  // Copy context to instance
  utils.extend(instance, context);

  return instance;
}

// Create the default instance to be exported
var axios = createInstance(defaults);
Copy the code

Portal:./lib/core/ axios.js

/**
 * Create a new instance of Axios
 *
 * @param {Object} instanceConfig The default config for the instance
 */
function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}
Copy the code

Let’s rearrange the logic of the above code:

  1. Use the default configuration provided by the librarydefaultsthroughAxiosThe constructor generates a context objectcontext;
  2. Declare a variableinstanceforAxios.prototype.requestAnd bindingthisforcontext;
  3. ininstanceExtends theAxios.prototypeProperties of anddefaultsandinterceptorsProperties;
  4. returninstanceThat is to sayAxiosThis is what the library exposesinstantceObject.

To summarize: Axios is essentially axios.prototype. request and extends axios.prototype’s properties and methods. Get is the axios.prototype method, so let’s open the axios.js file again to find out.

Portal:.lib/core/ axios.js

// Provide aliases for supported request methods
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
    }));
  };
});

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

As you can see from the code above, the get, POST, put methods on axios are aliases of axios.prototype. request, so HTTP requests made using AXIos are actually made by axios.prototype. request. This explains why there are two ways to make a GET request:

  • axios.get(url)
  • axios({method: 'get', url: url})

2.2 axios.get The process of initiating a request

We already know from the previous code that all axios requests are made through axios.prototype. request, so we find this method:

Portal:./lib/core/ axios.js

/** * Dispatch a request * * @param {Object} config The config specific for this request (merged with this.defaults) */ Axios.prototype.request = function request(config) { /*eslint no-param-reassign:0*/ // Allow for axios('example/url'[, config]) a la fetch API if (typeof config === 'string') { config = arguments[1] || {}; config.url = arguments[0]; } else { config = config || {}; } config = mergeConfig(this.defaults, config); // Set 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'; } // Note: here I delete the code irrelevant to the main process // the main function of the deleted code: // 1. // This is the big pity call and the rejectorcallback (); // This is the big pity call and the rejectorCallback (). if (! SynchronousRequestInterceptors) {/ / if the interceptor asynchronous invocation var chain = [dispatchRequest, undefined]; Array.prototype.unshift.apply(chain, requestInterceptorChain); chain.concat(responseInterceptorChain); promise = Promise.resolve(config); while (chain.length) { promise = promise.then(chain.shift(), chain.shift()); } // Return a chained call promise from the request interceptor. } // If the interceptor is called synchronously var newConfig = config; while (requestInterceptorChain.length) { var onFulfilled = requestInterceptorChain.shift(); var onRejected = requestInterceptorChain.shift(); try { newConfig = onFulfilled(newConfig); } catch (error) { onRejected(error); break; } } try { promise = dispatchRequest(newConfig); } catch (error) { return Promise.reject(error); } while (responseInterceptorChain.length) { promise = promise.then(responseInterceptorChain.shift(), responseInterceptorChain.shift()); } // return a chained call promise from dispatchRequest; };Copy the code

From the above code we know: The whole process is encapsulated in the REqeUST method using a promise, dispatchRequest(newConfig) is returned without an interceptor, that is, a request is made within dispatchRequest and a promise is returned, We find the code for dispatchRequest:

Portal:. / lib/core/dispatchRequest. Js

module.exports = function dispatchRequest(config) { throwIfCancellationRequested(config); / / deleted the headers and the data processing, click view source above portal var adapter = config. The adapter | | defaults. The adapter. Return adapter(config). Then (function onAdapterResolution(response) {return response; }, function onAdapterRejection(reason) {return promise.reject (reason); }); };Copy the code

DispatchRequest returns the result of adapter from config, and axios is generated by default, so we find the code for defaults.js:

Portal:./lib/defaults.js

Var defaults = {adapter: getDefaultAdapter(),} function getDefaultAdapter() {var adapter; if (typeof XMLHttpRequest ! == 'undefined') { // For browsers use XHR adapter adapter = require('./adapters/xhr'); } else if (typeof process ! == 'undefined' && Object.prototype.toString.call(process) === '[object process]') { // For node use HTTP adapter adapter  = require('./adapters/http'); } return adapter; }Copy the code

The adapter is an adapter, because AXIos relies on XHR objects in the browser and on the underlying HTTP library in the Node environment. Neither of these objects is based on promises, but AXIos wants to interact with them with promises, so the adapter is needed to make a compatibility. Provide a bridge for both sides of the interaction. We find the xhr.js code in the browser environment:

Portal:./lib/adapters/xhr.js

Module.exports = function xhrAdapter(config) {// export.exports = function xhrAdapter(config) {// export.exports = function xhrAdapter(config) DispatchXhrRequest (resolve, reject) {var request = new XMLHttpRequest(); / / constructs the request url. The open (config. The method. The toUpperCase (), buildURL (fullPath, config. Params, config. ParamsSerializer), true); // Set the request timeout in MS // Set the request timeout in MS // Request. Function onloadEnd () {if (! request) { return; } // Prepare the response var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null; var responseData = ! responseType || responseType === 'text' || responseType === 'json' ? request.responseText : request.response; var response = { data: responseData, status: request.status, statusText: request.statusText, headers: responseHeaders, config: config, request: request }; settle(resolve, reject, response); // Clean up request request = null; } if ('onloadend' in request) { // Use onloadend if available request.onloadend = onloadend; } else { // Listen for ready state to emulate onloadend request.onreadystatechange = function handleLoad() { 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 // Look at the comments here to handle the fact that browsers return a status code 0 if when using the file transfer protocol (request.status === 0 && ! (request.responseURL && request.responseURL.indexOf('file:') === 0)) { return; } // readystate handler is calling before onerror or ontimeout handlers, // so we should call onloadend on the next 'tick' setTimeout(onloadend); }; Onabort = function handleAbort() {// do STH... request = null; }; // Handle low level network errors request.onerror = function handleError() { // do sth ... request = null; }; // Handle timeout request.ontimeout = function handleTimeout() { // do sth ... request = null; }; // Send the request request.send(requestData); }); };Copy the code

The code above directly confirms the documentation that Axios is based on XHR objects in the browser, and it clearly shows how to wrap XHR with Promises.

Conclusion underaxiosThe process of initiating a request:

  1. inAxios.prototype.requestIn the method, theRequest interceptorThe synchronous/asynchronous returns differentpromise(One for the interceptorpromiseanddispatchRequestthepromise);
  2. indispatchRequesThe request header and the requested entity are processed in theadapterExecutionalpromise;
  3. Choose different ones by judging the circumstancesadapterIs used in the browserxhr.js;
  4. inxhr.jsDemonstrates the use ofXHRObject request process and event handling, andpromiseThe encapsulation process of.

2.3 Axios returns a Promise object

We’ve already seen the results when looking at how AXIos makes HTTP requests:

  • In cases where an asynchronous interceptor is used, the interceptor’s is returnedpromise;
  • Not using an asynchronous interceptor or not using an interceptor is returneddispatchRequestthepromiseThe bottom layer is usedpromiserightxhrThe encapsulation.

While looking at the xhr.js code, I wondered why the author used setTimeout(onloadEnd) in onReadyStatechange; Onloadend is called asynchronously because onReadyStatechange is called before onTimeout/onError, so if it were called synchronously, it would use settle to change the state of the promise. However, the author wants to handle errors not in settle, but through XHR events. Since I have never used XHR directly, I will verify here:

var xhr = new XMLHttpRequest();

xhr.timeout = 1;
xhr.open('get', '/api', true);
xhr.ontimeout = function () {
    console.log('timeout & redystate = ', xhr.readyState);
}

xhr.onerror = function () {
    console.log('onerror & redystate = ', xhr.readyState);
}

xhr.onreadystatechange = function () {
    console.log('onreadystatechange & redystate = ', xhr.readyState);
}

xhr.send(null)
Copy the code

The results are as follows:

To sum up: while watching xhr.js, I learned some pits using XHR objects. I also took this opportunity to learn XHR again on MDN, and benefited a lot

Iii. Summary and plan

conclusion

By reading the code, I learned:

  • AxiosThe code structure of the whole project and the general idea of the project;
  • AxiosHow to manage the interceptor, so that you can read the interceptor code next;
  • AxiosConstructors andaxiosObject properties and methods;
  • AxiosHow to use itPromiseEncapsulated;

plan

Later, the source code will be read step by step according to the feature given by the official website:

  • Intercept request and response
  • Transform request and response data
  • Cancel requests
  • Automatic transforms for JSON data
  • Client side support for protecting against XSRF