Axios source code analysis – XHR article

Article source hosted atgithubWelcome to fork!

Axios is a promise-based HTTP request library that can be used in browsers and Node.js and currently has a 42K star count on Github

Remark:

  1. Each section covers two aspects: How to use -> source code analysis
  2. [Tool method brief introduction] section can be skipped first, later used to come to see
  3. How does axios intercept request responses and modify request parameters and modify response data and how does Axios build xHR-based asynchronous Bridges with promises

Axios project directory structure

├ ─ ─ / dist /# project output directory├ ─ ─ / lib /# project source directory│ ├ ─ ─ / cancel /# Define the cancel function│ ├ ─ ─ / core /# Some core features│ │ ├ ─ ─ Axios. JsThe core main class of Axios│ │ ├ ─ ─ dispatchRequest. JsUse the HTTP request adapter method to send the request│ │ ├ ─ ─ InterceptorManager. JsInterceptor constructor│ │ └ ─ ─ settle. JsChange the state of the Promise based on the HTTP response state│ ├ ─ ─ / helpers# Some auxiliary methods│ ├ ─ ─ / adaptersDefine the requested adapter XHR, HTTP│ │ ├ ─ ─ HTTP. JsImplement the HTTP adapter│ │ └ ─ ─ XHR. JsImplement the XHR adapter│ ├ ─ ─ axios. Js# External exposure interface│ ├ ─ ─ defaults. Js# default configuration│ └ ─ ─ utils. Js# Utility├ ─ ─ package. Json# Project information├ ─ ─ the index, which sConfigure the TypeScript declaration file└ ─ ─ index. Js# import file

Copy the code

Note: Since all the code we need to look at is files in the /lib/ directory, we will look under /lib/ for any of the following file paths

Noun explanation

  • The interceptor interceptors

    (This makes sense if you’re familiar with middleware, as it serves the purpose of promise-based middleware.)

    Interceptors are divided into request interceptors and response interceptors, as the name implies: Request interceptors. Response Interceptors can intercept each HTTP request after each HTTP request and modify the return item.

    This is explained briefly, and will be explained in more detail later on how to intercept the request response and modify the request parameters to modify the response data.

  • Data converter (essentially converting data, such as an object to a JSON string)

    Data converters are divided into request converters and response converters. As the name implies, a request converter (transformRequest) converts data before a request, and a response converter (transformResponse) converts data after a response.

  • HTTP request adapter (essentially a method)

    In the AXIos project, there are two main types of HTTP request adapters: XHR and HTTP. The core of XHR is the XMLHttpRequest object of the browser, and the core of HTTP is the HTTP [s].request method of Node

    Axios, of course, leaves it up to the user to configure the interface to the adapter through Config, but in general, these two adapters can handle requests from the browser to the server or from the NODE HTTP client to the server.

    This sharing focuses on XHR.

  • Config configuration item (essentially an object)

    Here we say config, in the project is not really called config variable name, this name is based on its use, easy to understand the name.

    In the axios project, when setting \ to read config, it’s called defaults(/lib/defaults.js), which is the default configuration item, or config, as in axios.prototype. request, Another example is the parameter of the xhrAdapterhttp request adapter method.

    Config is a very important link in the Axios project, serving as the main bridge between users and axios project internal “communication”.

A flow chart of the inner workings of Axios

This section describes the tools and methods

(Note: this section can be skipped and used later.)

There are some methods that are used in multiple places throughout the project, so I’ll briefly describe them

  1. Bind: To assign a context to a function, that is, to this

bind(fn, context); 

Copy the code

Function.prototype.bind: fn. Bind (context)

  1. ForEach: Traverses groups or objects

var utils = require('./utils');
var forEach = utils.forEach;

/ / array
utils.forEach([], (value, index, array) => {})

/ / object
utils.forEach({}, (value, key, object) => {})

Copy the code
  1. Merge: Deeply merge multiple objects into one object

var utils = require('./utils');
var merge = utils.merge;

var obj1 = {
  a: 1.b: {
    bb: 11.bbb: 111,}};var obj2 = {
  a: 2.b: {
    bb: 22,}};var mergedObj = merge(obj1, obj2); 

Copy the code

The mergedObj object is:


{ 
  a: 2.b: { 
    bb: 22.bbb: 111}}Copy the code
  1. Extend: Extends the methods and properties of one object to another, specifying the context

var utils = require('./utils');
var extend = utils.extend;

var context = {
  a: 4};var target = {
  k: 'k1',
  fn(){
    console.log(this.a + 1)}};var source = {
  k: 'k2',
  fn(){
    console.log(this.a - 1)}};let extendObj = extend(target, source, context);

Copy the code

ExtendObj objects are:


{
  k: 'k2'.fn: source.fn.bind(context),
}

Copy the code

Perform extendObj. Fn (); To print 3

Why does Axios have multiple uses

How to use


// Import the axios package first
import axios from 'axios'

Copy the code

First use: axios(option)


axios({
  url,
  method,
  headers,
})

Copy the code

Second use: axios(url[, option])


axios(url, {
  method,
  headers,
})

Copy the code

Third use (for get, delete, etc.) : axios[method](url[, option])


axios.get(url, {
  headers,
})

Copy the code

4. Axios [method](URL [, data[, option]])


axios.post(url, data, {
  headers,
})

Copy the code

Number 5: axios.request(option)


axios.request({
  url,
  method,
  headers,
})

Copy the code

Source code analysis

As the entry file for the Axios project, let’s take a look at the source code of axios.js. The core method that enables multiple uses of axios is the createInstance method:


// /lib/axios.js
function createInstance(defaultConfig) {
  // Create an Axios instance
  var context = new Axios(defaultConfig);

  / / the following code can also be implemented: var instance = Axios. Prototype. Request. Bind (context);
  // Instance refers to the request method, and the context refers to the context, so it can be called directly with instance(option)
  // axios.prototype. request specifies the datatype of the first parameter, which allows us to call instance(URL, option)
  var instance = bind(Axios.prototype.request, context);

  // Extend the axios.prototype method to instance,
  // So instance has get, post, put, etc
  // Specify the context as context so that when the method on the Axios prototype chain is executed, this points to the context
  utils.extend(instance, Axios.prototype, context);

  // Extend the context object's own properties and methods to instance
  // Note: Because the forEach method that extends internally does for in traversal of the object, it traverses only properties of the object itself, not properties on the prototype chain
  // In this case, instance has the defaults and interceptors attributes. (These two properties will be covered later)
  utils.extend(instance, context);

  return instance;
}

// Take the default configuration items as parameters (described below) and create an Axios instance, which will eventually be exported as an object
var axios = createInstance(defaults);

Copy the code

This code looks confusing, but createInstance wants to get a Function that points to axios.prototype. request, This Function will also have each method on Axios.Prototype as a static method, and the context of each method will point to the same object.

The source code for Axios, axios.prototype. request, Axios.

Axios is the core of the Axios package. An instance of Axios is an Axios application, and all other methods are extensions of Axios content. The core method of the Axios constructor is the Request method


// /lib/core/Axios.js
function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}

Axios.prototype.request = function request(config) {
  / /... Omit code
};

// Provide aliases for supported request methods
utils.forEach(['delete'.'get'.'head'.'options'].function forEachMethodNoData(method) {
  Axios.prototype[method] = function(url, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url
    }));
  };
});
utils.forEach(['post'.'put'.'patch'].function forEachMethodWithData(method) {
  Axios.prototype[method] = function(url, data, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url,
      data: data
    }));
  };
});

Copy the code

With the above code, we can make HTTP requests in various ways: axios(), axios.get(), axios.post()

In general, the project will use the axios instance exported by default to meet the requirements. If not, a new AXIOS instance will be created. The axios package also reserves the interface, as shown in the following code:


// /lib/ axios.js-31
axios.Axios = Axios;
axios.create = function create(instanceConfig) {
  return createInstance(utils.merge(defaults, instanceConfig));
};

Copy the code

When using axios, both the get and post methods end up calling the axios.prototype. request method. How does this method make requests based on our config?

Before we start talking about axios.prototype. request, let’s get back to how user-configured config works in the Axios project.

How does the user-configured Config work

By config, we mean the configuration item object that runs through the project, through which we can set:

HTTP request adapter, request address, request method, request header, request data, transformation of request or response data, request progress, HTTP status code verification rules, timeout, request cancellation, etc

As you can see, almost all axios functionality is configured and passed through this object, both as a communication bridge within the AXIOS project and between users and AXIOS.

First, let’s look at how users can define configuration items:


import axios from 'axios'

// First: directly modify the defaults property on Axios instances, mainly to set the general configuration
axios.defaults[configName] = value;

// Type 2: the axios.prototype. request method is eventually called when the request is made, and the configuration item is passed in, mainly to set the "case" configuration
axios({
  url,
  method,
  headers,
})

// Create a new instance of Axios and pass in the configuration item
let newAxiosInstance = axios.create({
  [configName]: value,
})

Copy the code

Take a look at the axios.prototype. request method: (/lib/core/ axios.js – line 35)


config = utils.merge(defaults, {method: 'get'}, this.defaults, config);

Copy the code

You can see that there is a combination of the default configuration object defaults (/lib/defaults.js), the Axios instance property this.defaults, and the request request parameter config.

Defaults (/lib/defaults.js) — > {method: ‘get’} — > Axios instance attribute this.defaults — > Request request parameter config

I’ll leave you with a question to consider: when are defaults and this.defaults configured the same, and when are they different?

Now that we have the merge config object, how is this object passed in the project?


Axios.prototype.request = function request(config) {
  // ...
  config = utils.merge(defaults, {method: 'get'}, this.defaults, config);

  var chain = [dispatchRequest, undefined];
  // Pass the config object as an argument to the Primise. Resolve method
  var promise = Promise.resolve(config);

  / /... Omit code
  
  while (chain.length) {
    // Config will sequentially pass the request interceptor -dispatchRequest method -response interceptor
    // Interceptors and dispatchRequest methods are covered in a special section below.
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
};

Copy the code

Config has now completed its legendary life – – the next section will get to the highlight: Axios.prototype.request

axios.prototype.request

The code is complex and some of the methods need to be traced back to the source, so a simple understanding of the chain array is good. The interceptors involved and [dispatchRequest] will be described in detail later

The chain array is used to hold the interceptor method and dispatchRequest method, and the new promise is returned in axios.prototype. request through the sequential callback function from the chain array. And passing a response or error, that’s what Axios.prototype.request does.

View source code:


// /lib/core/Axios.js
Axios.prototype.request = function request(config) {
  // ...
  var chain = [dispatchRequest, undefined];
  var promise = Promise.resolve(config);

  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });
  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });

  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
};

Copy the code

At this point, you must be curious about the interceptor. What is it? Let’s find out next time

How to intercept request response and modify request parameters modify response data

How to use


// Add request interceptor
const myRequestInterceptor = axios.interceptors.request.use(config= > {
    // What to do before sending the HTTP request
    return config; // There is and must be a config object returned
}, error => {
    // What to do about the request error
    return Promise.reject(error);
});

// Add a response interceptor
axios.interceptors.response.use(response= > {
  // What to do with the response data
  return response; // There is and must be a Response object returned
}, error => {
  // Do something about the response error
  return Promise.reject(error);
});

// Remove an interceptor
axios.interceptors.request.eject(myRequestInterceptor);

Copy the code

thinking

  1. Can I return error directly?

axios.interceptors.request.use(config= > config, error => {
  // Can I return error directly?
  return Promise.reject(error); 
});

Copy the code
  1. How do I implement the chain call of promise

new People('whr').sleep(3000).eat('apple').sleep(5000).eat('durian');

// Print the result
/ / (wait for 3 s) - - > 'WHR eat apple - (wait for 5 s) - >' WHR eat durian

Copy the code

Source code analysis

Interceptors are explained briefly in the noun explanation section.

Each AXIos instance has an interceptors instance attribute, and the interceptors object has two attributes: Request and Response.


function Axios(instanceConfig) {
  // ...
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}

Copy the code

Both of these attributes are an instance of the InterceptorManager, and the InterceptorManager constructor is used to manage interceptors.

Let’s take a look at the InterceptorManager constructor:

The InterceptorManager constructor is used to implement the interceptor. This constructor prototype has three methods: use, eject, and forEach. The source code is actually quite simple, operating on the Handlers instance properties of the constructor.


// /lib/core/InterceptorManager.js

function InterceptorManager() {
  this.handlers = []; Each item in the array is an object with two properties corresponding to the function executed on success and failure.
}

// Add interceptor method to interceptor
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected
  });
  return this.handlers.length - 1;
};

// Unregister the specified interceptor
InterceptorManager.prototype.eject = function eject(id) {
  if (this.handlers[id]) {
    this.handlers[id] = null; }};Handlers are iterated through and each item in this. Handlers is passed to fn as an argument
InterceptorManager.prototype.forEach = function forEach(fn) {
  utils.forEach(this.handlers, function forEachHandler(h) {
    if(h ! = =null) { fn(h); }}); };Copy the code

So when we pass axios. Interceptors. Request. Use add interceptors, axios internal is how to make these interceptors can before the request, the request to get the data we want?

Take a look at the code:


// /lib/core/Axios.js
Axios.prototype.request = function request(config) {
  // ...
  var chain = [dispatchRequest, undefined];

  // Initialize a Promise object, state micro Resolved, microconfig object for the parameters received
  var promise = Promise.resolve(config);

  // pay attention to: interceptor. This is a big pity or interceptor
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });

  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });

  // Add the interceptor and the chain array will look like this:
  / / /
  // requestFulfilledFn, requestRejectedFn, ... .
  // dispatchRequest, undefined,
  // responseFulfilledFn, responseRejectedFn, .... .
  // ]

  // Execute the while loop as long as the chain array length is not zero
  while (chain.length) {
    The shift() method removes the first element from the array and returns the value of the first element.
    // Each time we execute the while loop, we sequentially extract two items from the chain array as the first and second arguments to the promise.then method

    / / as we use InterceptorManager. Prototype. Use add rules of the interceptor, just add each is our through InterceptorManager. The prototype, the use method to add the success and failure of callback

    / / by InterceptorManager. Prototype. Use add interceptors in the interceptor array push method used in the array,
    // For request interceptors, unshift is used to add interceptors to the chain array, shift is used to remove interceptors from the chain array
    // For responder interceptors, the chain array is added to by shift, and the chain array is removed by shift

    // The big pity function of the first request interceptor will receive the Config object passed in when the Promise object is initialized, and the request interceptor specifies that the user-written pity function must return a Config object, so when implementing the chain call through the Promise, The FULFILLED function of each request interceptor receives a Config object

    // The big pity function of the first response interceptor will accept the data (i.e., the response object) requested by dispatchRequest (which is our request method), and the response interceptor specifies that the user must return a response object. So when chain calls are implemented through promises, each response interceptor's depressing function will receive a response object

    // An error thrown by a dispatchRequest is received by the response interceptor (rejected) function.

    // Since AXIos is a chained call via promise, we can do it asynchronously in the interceptor, and the interceptor will execute in the same order as above, i.e. the dispatchRequest method must wait for all request interceptors to execute. The response interceptor must wait for dispatchRequest to finish before it starts executing.

    promise = promise.then(chain.shift(), chain.shift());

  }

  return promise;
};

Copy the code

Now that you know what an interceptor is and how it works in the axios.prototype. request method, how does a dispatchRequest “midstream” send an HTTP request?

What dispatchRequest does

DispatchRequest does 3 things: 1. Take the config object and do the final processing of the config before passing it to the HTTP request adapter. 2. The HTTP request adapter initiates a request based on the config configuration. 3. The HTTP request adapter initiates a request based on the header, data, and config.transformResponse ( The following data converters will explain) get the response after the data conversion and return.


// /lib/core/dispatchRequest.js
module.exports = function dispatchRequest(config) {
  throwIfCancellationRequested(config);

  // Support baseURL config
  if(config.baseURL && ! isAbsoluteURL(config.url)) { config.url = combineURLs(config.baseURL, config.url); }// Ensure headers exist
  config.headers = config.headers || {};

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

  // Merge the headers
  config.headers = utils.merge(
    config.headers.common || {},
    config.headers[config.method] || {},
    config.headers || {}
  );

  // Delete unnecessary attributes in the header attribute
  utils.forEach(
    ['delete'.'get'.'head'.'post'.'put'.'patch'.'common'].function cleanHeaderConfig(method) {
      deleteconfig.headers[method]; });// The HTTP request adapter will use the custom adapter on config in preference to the default XHR or HTTP adapter when not configured, but most of the time the default adapter provided by AXIos will work for us
  var adapter = config.adapter || defaults.adapter;

  return adapter(config).then(/ * * /);
};

Copy the code

Okay, now that we’ve seen this, it’s time to tease out: How does Axios bridge xHR-based asynchronous communication with promises?

How does Axios bridge xHR-based asynchrony with promises

How does Axios handle this asynchronously through promises?

How to use


import axios from 'axios'

axios.get(/ * * /)
.then(data= > {
  // Get the data requested from the server
})
.catch(error= > {
  // Here you can get the error object that failed or cancelled the request or other failed processing
})

Copy the code

Source code analysis

Here’s a quick look at the sequence of HTTP requests that arrive at the user in an AXIos project:

As we can see from how Axios can be used in multiple ways, no matter how a user calls Axios, they end up calling the axios.prototype. request method, which ultimately returns a Promise object.


Axios.prototype.request = function request(config) {
  // ...
  var chain = [dispatchRequest, undefined];
  // Pass the config object as an argument to the Primise. Resolve method
  var promise = Promise.resolve(config);

  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
};

Copy the code

The axios.prototype. request method calls dispatchRequest. The dispatchRequest method calls xhrAdapter, which returns a Promise object


// /lib/adapters/xhr.js
function xhrAdapter(config) {
  return new Promise(function dispatchXhrRequest(resolve, reject) {
    / /... Omit code
  });
};

Copy the code

The XHR in the xhrAdapter executes the Promise object’s resolve method and passes the requested data, while the XHR in the xhrAdapter executes its Reject method and passes the error message as a parameter.


// /lib/adapters/xhr.js
var request = new XMLHttpRequest();
var loadEvent = 'onreadystatechange';

request[loadEvent] = function handleLoad() {
  // ...
  // Go down to the source code for settle
  settle(resolve, reject, response);
  // ...
};
request.onerror = function handleError() {
  reject(/ * * /);
  request = null;
};
request.ontimeout = function handleTimeout() {
  reject(/ * * /);
  request = null;
};

Copy the code

Verify that the result returned by the server is validated:


// /lib/core/settle.js
function settle(resolve, reject, response) {
  var validateStatus = response.config.validateStatus;
  if(! response.status || ! validateStatus || validateStatus(response.status)) { resolve(response); }else {
    reject(/ * * /); }};Copy the code

Back in the dispatchRequest method, we first get the Promise object returned by the xhrAdapter method. Then, we process the success or failure of the Promise object returned by the xhrAdapter. If the response fails, a Promise object in the rejected state is returned.


  return adapter(config).then(function onAdapterResolution(response) {
    // ...
    return response;
  }, function onAdapterRejection(reason) {
    // ...
    return Promise.reject(reason);
  });
};

Copy the code

So at this point, when the user calls the axios() method, he can directly call the Promise’s.then or.catch for business processing.

Going back to the data transformation we talked about in the dispatchRequest section, and axios officially introduces data transformation as a highlight, how does data transformation work with Axios?

Data Converter – Converts request and response data

How to use

  1. Modify the global converter

import axios from 'axios'

// Add a conversion method to an existing request converter
axios.defaults.transformRequest.push((data, headers) = > {
  / /... Processing the data
  return data;
});

// Override the request converter
axios.defaults.transformRequest = [(data, headers) = > {
  / /... Processing the data
  return data;
}];

// Add a conversion method to an existing response converter
axios.defaults.transformResponse.push((data, headers) = > {
  / /... Processing the data
  return data;
});

// Override the response converter
axios.defaults.transformResponse = [(data, headers) = > {
  / /... Processing the data
  return data;
}];

Copy the code
  1. Modifies the converter for an AXIOS request

import axios from 'axios'

// Add a conversion method to an existing converter
axios.get(url, {
  // ...
  transformRequest: [
    ...axios.defaults.transformRequest, // Remove this line of code to rewrite the request converter
    (data, headers) => {
      / /... Processing the data
      returndata; }].transformResponse: [
    ...axios.defaults.transformResponse, // Removing this line of code is equivalent to overwriting the response converter
    (data, headers) => {
      / /... Processing the data
      returndata; }],})Copy the code

Source code analysis

A request converter and a response converter are already defined in the default defaults configuration.


// /lib/defaults.js
var defaults = {

  transformRequest: [function transformRequest(data, headers) {
    normalizeHeaderName(headers, 'Content-Type');
    // ...
    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; }].transformResponse: [function transformResponse(data) {
    if (typeof data === 'string') {
      try {
        data = JSON.parse(data);
      } catch (e) { /* Ignore */}}returndata; }]};Copy the code

So where is the converter used in the Axios project?

The request converter is used to process the request data before the HTTP request and then pass it to the HTTP request adapter for use.


// /lib/core/dispatchRequest.js
function dispatchRequest(config) {
  
  config.data = transformData(
    config.data,
    config.headers,
    config.transformRequest
  );

  return adapter(config).then(/ *... * /);
};

Copy the code

Take a look at the transformData method, which iterates through an array of converters, executing each converter separately and returning a new data based on the data and HEADERS arguments.


// /lib/core/transformData.js
function transformData(data, headers, fns) {
  utils.forEach(fns, function transform(fn) {
    data = fn(data, headers);
  });
  return data;
};

Copy the code

The response converter is used to convert data from the HTTP request adapter after the HTTP request is completed:


// /lib/core/dispatchRequest.js
return adapter(config).then(function onAdapterResolution(response) {
    // ...
    response.data = transformData(
      response.data,
      response.headers,
      config.transformResponse
    );

    return response;
  }, function onAdapterRejection(reason) {
    if(! isCancel(reason)) {// ...
      if(reason && reason.response) { reason.response.data = transformData( reason.response.data, reason.response.headers, config.transformResponse ); }}return Promise.reject(reason);
  });

Copy the code

The relationship between converters and interceptors?

Interceptor can also realize the requirements of converting request and response data, but according to the author’s design and comprehensive code, it can be seen that during the request, interceptor is mainly responsible for modifying config configuration items, and data converter is mainly responsible for converting request body, for example, converting object to string. After the request and response, interceptor can get response. The data converter is mainly responsible for processing the response body, such as converting strings into objects.

Axios officially introduces “automatic conversion to JSON data” as a separate feature, but how does the data converter do this? It’s actually pretty simple. Let’s take a look.

Automatically convert JSON data

By default, AXIos will automatically serialize the incoming data object into a JSON string and convert the JSON string in the response data into a JavaScript object

Source code analysis


// When requested, the data is converted to a JSON string
// /lib/defaults.js 
transformRequest: [function transformRequest(data, headers) {
  // ...
    if (utils.isObject(data)) {
      setContentTypeIfUnset(headers, 'application/json; charset=utf-8');
      return JSON.stringify(data);
    }
    return data;
}]

// When the response is received, the requested data is converted into a JSON object
// /lib/defaults.js
transformResponse: [function transformResponse(data) {
    if (typeof data === 'string') {
      try {
        data = JSON.parse(data);
      } catch (e) { /* Ignore */}}return data;
}]

Copy the code

Now that we’ve covered the axios project workflow, let’s take a look at some other skill points that axios brings to the table.

The header is set

How to use


import axios from 'axios'

// Set the generic header
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; / / XHR logo

// Set a header for a request
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8';

// Set the header of a request
axios.get(url, {
  headers: {
    'Authorization': 'whr1',}})Copy the code

Source code analysis


/ / / lib/core/dispatchRequest js - 44

  config.headers = utils.merge(
    config.headers.common || {},
    config.headers[config.method] || {},
    config.headers || {}
  );

Copy the code

How do I cancel a request that has been sent

How to use


import axios from 'axios'

// The first cancellation method
axios.get(url, {
  cancelToken: new axios.CancelToken(cancel= > {
    if (/* Cancel the condition */) {
      cancel('Unlog'); }})});// The second cancellation method
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get(url, {
  cancelToken: source.token
});
source.cancel('Unlog');

Copy the code

Source code analysis


// /cancel/ canceltoken.js - line 11
function CancelToken(executor) {
 
  var resolvePromise;
  this.promise = new Promise(function promiseExecutor(resolve) {
    resolvePromise = resolve;
  });
  var token = this;
  executor(function cancel(message) {
    if (token.reason) {
      return;
    }
    token.reason = new Cancel(message);
    resolvePromise(token.reason);
  });
}

// /lib/adapters/ xhr.js-159 line
if (config.cancelToken) {
    config.cancelToken.promise.then(function onCanceled(cancel) {
        if(! request) {return;
        }
        request.abort();
        reject(cancel);
        request = null;
    });
}

Copy the code

This. Promise = new Promise(resolve => resolvePromise = resolve); Now that promise is in a pending state and with this property, In the/lib/adapters/XHR. Js file continue to add to this promise instance. Then methods (XHR. Js file of 159 line config. CancelToken. Promise. Then (message = > Request. The abort ()));

Outside of CancelToken, control of the Cancel method is obtained with the Executor argument. When CancelToken is executed, the instance’s promise property is changed to Rejected, and request.abort() is called.

The second writing method above can be regarded as the improvement of the first writing method, because most of the time we cancel the request method is used outside this request method, for example, send A, B two requests, when B request is successful, cancel A request.


// The first way is:
let source;
axios.get(Aurl, {
  cancelToken: new axios.CancelToken(cancel= >{ source = cancel; })}); axios.get(Burl) .then((a)= > source('REQUEST B is successful'));

// The second way:
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get(Aurl, {
  cancelToken: source.token
});
axios.get(Burl)
.then((a)= > source.cancel('REQUEST B is successful'));

Copy the code

I prefer the first one because the second one is more subtle and intuitive than the first one.

Problems found
  1. In /lib/adapters/xhr.js, don’t you call the onCanceled parameter message? Cancel?

  2. In the /lib/ Adapters /xhr.js file, onCanceled or reject should also send config information

Cookies are carried across domains

How to use


import axios from 'axios'

axios.defaults.withCredentials = true;

Copy the code

Source code analysis

We is how to play a role in the user configuration config section has introduced the config in axios project delivery process, thus, we through axios. Defaults. WithCredentials = true do configuration, You can get it in /lib/adapters/xhr.js and then configure it to the XHR object using the following code.


var request = new XMLHttpRequest();

// /lib/adapters/xhr.js
if (config.withCredentials) {
  request.withCredentials = true;
}

Copy the code

Timeout configuration and handling

How to use


import axios from 'axios'

axios.defaults.timeout = 3000;

Copy the code

Source code analysis


// /adapters/xhr.js
request.timeout = config.timeout;

// /adapters/xhr.js
// Combine the error information into a string with createError
request.ontimeout = function handleTimeout() {
  reject(createError('timeout of ' + config.timeout + 'ms exceeded', 
    config, 'ECONNABORTED', request));
};

Copy the code
  • How do I add post-timeout processing outside the AXIos library

axios().catch(error= > {
  const { message } = error;
  if (message.indexOf('timeout') > - 1) {// Timeout processing}})Copy the code

Overwrite the validation success or failure rule validatestatus

Customize the success and failure ranges of HTTP status codes

How to use


import axios from 'axios'

axios.defaults.validateStatus = status= > status >= 200 && status < 300;

Copy the code

Source code analysis

In the default configuration, the default HTTP status code validation rules are defined, so customizing validateStatus is a rewrite of this method


// `/lib/defaults.js`
var defaults = {
  // ...
  validateStatus: function validateStatus(status) {
    return status >= 200 && status < 300;
  },
  // ...
}

Copy the code

When did AXIos start validating HTTP status codes?


// /lib/adapters/xhr.js
var request = new XMLHttpRequest();
var loadEvent = 'onreadystatechange';

// /lib/adapters/xhr.js
// The onReadyStatechange event is emitted whenever readyState changes
request[loadEvent] = function handleLoad() {
  if(! request || (request.readyState ! = =4 && !xDomain)) {
    return;
  }
  / /... Omit code
  var response = {
      // ...
      // IE sends 1223 instead of 204 (https://github.com/axios/axios/issues/201)
      status: request.status === 1223 ? 204 : request.status,
      config: config,
  };
  settle(resolve, reject, response);
  / /... Omit code
}

Copy the code

// /lib/core/settle.js
function settle(resolve, reject, response) {
  // The config object validateStatus is either our custom validateStatus method or the default validateStatus method
  var validateStatus = response.config.validateStatus;
  // If validateStatus passes, the resolve method is triggered
  if(! response.status || ! validateStatus || validateStatus(response.status)) { resolve(response); }else {
    reject(createError(
      'Request failed with status code ' + response.status,
      response.config,
      null, response.request, response )); }};Copy the code

conclusion

In the axios project, there are many clever uses of JS, such as the concatenation of promises (of course, you can also say that this is borrowed from many asynchronous middleware processing methods), which makes it easy to control the flow of various processing methods before and after the request. Many practical small optimizations, such as pre – and post-request data processing, save programmers from having to write json.xxx again and again; Support for both browser and Node environments is great for projects using Node.

In a word, this star can get 42K+ (as of 2018.05.27) on Github, the strength is not cover, is worth a good heart!