Daily development requires front-end and back-end data interaction, so understanding a request library is critical. This article analyzes AXIOS deeply through the interpretation of axiOS request library usage and feature source code, and takes you to master AXIos thoroughly.

Features

  • Make XMLHttpRequests from the browser
  • Make http requests from node.js
  • Supports the Promise API
  • Intercept request and response
  • Transform request and response data
  • Cancel requests
  • Automatic transforms for JSON data
  • Client side support for protecting against XSRF

Config order of precedence

The following priorities are in ascending order

  • Axios default configuration, in lib/defaults.js
  • global axios defaults
axios.defaults.baseURL = 'https://api.example.com';
Copy the code
  • Custom instance defaults
const instance = axiso.create({
    baseURL: 'https://api.example.com'
});
Copy the code
  • request config
instance.get('/users', {
    baseURL: 'https://api.github.com'
})
Copy the code

Supports the Promise API

Usage

// Promise 
instance.get('/users').then(res= > {
    console.log(res);
})
// Async | Await
const res = await instance.get('/users');
console.log(res);
Copy the code

Source

// Take adapter/xhr.js as an example
function xhrAdapter (config) {
    return new Promise(function dispatchXhrRequest(resolve, reject) {
        let requestData = config.data;
        let request = new XMLHttpRequest();
        request.open(config.method.toUpperCase(), URL, true); // The request has not yet been sent
        request.timeout = config.timeout;
        request.onreadystatechange = function handleLoad() {
            if(! request || request.readyState ! = =4) {
                return;
            }
            const response = {
                data,
                status: request.status,
                statusText: request.statusText,
                headers,
                config,
                request
            };
            settle(resolve, reject, response);
            request = null;
        }
        if(! requestData) { requestData =null;
        }
        // send the request
        request.send(requestData);
    });
}
// Resolve or reject a Promise based on response status
function settle(resolve, reject, response) {
    const validateStatus = response.config.validateStatus;
    if(! response.status || ! validateStatus || validateStatus(response.status)) { resolve(response); }else {
        reject(createError('... ')); }}Copy the code

Adapter

Node.js is based on V8 JavaScript Engine. The top-level object is Global. There is no window object or browser host, so XMLHttpRequest object or FETCH API cannot be used. For request libraries that need to adapt to both the browser and node.js environment, use HTTP in the Node.js environment and XMLHttpRequest/Fetch in the browser. Adaptors for different environments were created. Code lib/ Adapters

Usage

const res = await instance.get('/users', {
    adapter: function customAdapter(config) {
        returnfetch(config.url); }});Copy the code

Source

function dispatchRequest (config) {
    const adapter = config.adapter || defaults.adapter; // You can customize adapters in the configuration, priority custom adapter > Default adapter
    return adapter(config).then(function onAdapterResolution(response) {
        // ...
    }, function onAdapterRejection(reason) {
    	// ...})}const defaults = {
    adapter: getDefaultAdapter()
};
// Use different adapters depending on the environment
function getDefaultAdapter () {
    let adapter;
    if (typeofXMLHttpRequest ! = ='undefined') {
        // For browsers use XHR adapter
        adapter = require('./adapter/xhr');    
    } else if (typeofprocess ! = ='undefined' && Object.prototype.toString.call(process) === '[object process]') {
    	// For node use HTTP adapter
        adapter = require('./adapter/http');
    }
    return adapter;
}
Copy the code

Transform request and response data

Usage

const res = await instance.get('/user', {
    transformRequest: [function (data, headers) {
        // Do whatever you want to transform the data
        returndata; }].transformResponse: function (data, headers) {
        // Do whatever you want to transform the data
    	return JSON.parse(data); }});Copy the code

Source

function dispatchRequest(config) {
    // Transform request data
    config.data = transformData(config.data, config.headers, config.transformRequest); // Customize the request conversion function
    return adapter(config).then(function onAdapterResolution(response) {
        // Transform response data
        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); }})})}/ * * @ param {Array | Function} FNS: A single Function or an Array of functions provides the to transform the data by data and headers * /
function transformData(data, headers, fns) {
    utils.forEach(fns, function transform(fn) {
        data = fn(data, headers);
    });
    return data;
}
Copy the code

Automatic transforms for JSON data

Source

const defaults = {
    transformResponse: [function transformResponse(data) {
        // Automatically parse the JSON string
        if (typeof data === 'string') {
            data = JSON.parse(data);
        }
        returndata; }};Copy the code

Interceptors

Usage

// Interceptor registration
// Add a request interceptor
axios.interceptors.request.use(function (config) {
    // Do something before request is sent
    return config;
}, function (error) {
    // Do something with request error
    return Promise.reject(error);
});
// Add a response interceptor
axios.interceptors.response.use(function (response) {
    // Do something with response data
    return response;
  }, function (error) {
    // Do something with response error
    return Promise.reject(error);
});
Copy the code

Source

/ / interceptor collection/lib/core/InterceptorManager js] (https://github.com/axios/axios/blob/master/lib/core/InterceptorManager.js)
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
    this.handlers.push({
        fulfilled: fulfilled,
        rejected: rejected
    });
    return this.handlers.length - 1;
};
// Interceptor firing
Axios.prototype.request = function request(config) {
    const chain = [dispatchRequest, undefined];
    const 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);
    });    
    // chain = [requestFulfilled, requestRejected, dispatchRequest, undefined, responseFulfilled, responseRejected]
    while(chain.length) {
        promise = promise.then(chain.shift(), chain.shift());
    }
    return promise;
}
// Interceptor sequence: Register => collect => trigger
// Trigger order: request interceptor => Request => response interceptor
Copy the code

Cancel Requests

Axios passes a cancelToken in config. The cancelToken. Promise is actually a promise instance in a PENDING state. When the user calls the cancel method, Causes the Promise instance to change its PENDING state to RESOLVE, triggers the listener onCanceled, calls Request.Abort (), and cancels XHR.

Usage

// Carried when sending the request
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
instance.get('/user/12345', {
  cancelToken: source.token // Can only be cancelToken
});
// Cancel the request
source.cancel();
Copy the code

Source

function CancelToken(executor) {
    let resolvePromise;
    this.promise = new Promise(resolve= > {
        resolvePromise = resolve;
    });
    const token = this;
    executor(function cancel(message) {
    	if (token.reason) return;
        token.reason = new Cancel(message);
        resolvePromise(token.reason)
    });
}
/** * Returns an object that contains a new `CancelToken` and a function that, when called, * cancels the `CancelToken`. */
CancelToken.source = function source() {
  let cancel; // Cancel is the function cancel method above. When cancel is performed, the resolvePromise method is called
  const token = new CancelToken(function executor(c) {
      cancel = c;
  });
  return {
      token: token,
      cancel: cancel
  };
};
// xhr.js 或者 http.js
if (config.cancelToken) {
      // Handle cancellation
      config.cancelToken.promise.then(function onCanceled(cancel) {
          if(! request) {return;
          }
          request.abort();
          reject(cancel);
          // Clean up request
          request = null;
      });
}
Copy the code

XSRF

XSRF, also called CROSS-site Request Forgery (CSRF), is an attack that hijacks trusted users to send unexpected requests to the server. Under normal circumstances, CSRF attack means that the attacker uses the victim’s Cookie to win the trust of the server and sends forged requests to the attacked server in the victim’s name without the victim’s knowledge, so as to perform operations under permission protection without authorization. Generally, the following three ways are used to prevent:

  • Verification code
  • Referer Check
  • Token

The AXIos library takes the approach of passing tokens in requests to protect against XSRF attacks.

// Axios is configured by default. Custom xsrfCookieName and xsrfHeaderName are passed in
const defaults = {
    xsrfCookieName: 'XSRF-TOKEN'.xsrfHeaderName: 'X-XSRF-TOKEN'
};
// add XSRF header
if (utils.isStandardBrowserEnv()) {
    const xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath)) && config.xsrfCookieName ? cookies.read(config.xsrfCookieName) : undefined;
    if(xsrfValue) { requestHeaders[config.xsrfHeaderName] = xsrfValue; }}Copy the code

reference

  • Learn Axios: Encapsulate a neatly structured Fetch library
  • Let’s talk about XSS and CSRF
  • axios README