“This is the 27th day of my participation in the Gwen Challenge in November. See details of the event: The Last Gwen Challenge in 2021”

An overview of the

Axios is a Promise-based HTTP library that can be used in browsers and Node.js. Moreover, it has the following characteristics:

  • Intercept requests and responses
  • Transform request data and response data
  • Cancel the request
  • Automatically convert JSON data
  • The client supports XSRF defense

This article will delve into the source code, detach from Axios’s core data model, and learn what it can teach. Understand how interceptors, adapters, and cancellation request modules are implemented through the source code. Finally, the essence of Axios library will be mastered step by step by implementing the function of Axios caching interface data. After reading this, you will be able to implement interface throttling, request failure retransmission, cancel repeated requests and other requirements.

Axios design understanding

First, let’s take a look at the general data flow using an AXIOS instance to initiate a URL request.

Tracing the full axios request process source code, you can see that the project has extracted a number of data models and transformation modules, such as axios constructors, defaults configuration parameters, interceptors, adapters, and data transformation functions, with a high level of abstraction.

At the same time, it has a good hierarchical architecture, which means the perfect decoupling of the low-level requesting capabilities from the upper-level business enhancement capabilities. The idea of an adapter at the bottom makes the project perfectly compatible with the browser side and the Node.js server side. In the upper-layer service instance configuration, the default configuration, instance configuration, and method configuration are read in sequence and overwritten from the bottom up to make the instance more flexible and fine in scalability. On the execution link of the request method, there is a clear process design. Of course, axiOS also benefits from the high abstraction of the data model, which makes it more convenient to do business on the process link without harm and intrusion. For example, the adapter configured in the defaults defaults configuration item is an encapsulation of the underlying request, and there is no intrusion from the upper layer. Therefore, the adapter can be enhanced to inject more extension capabilities into the real URL, for example: Request failure retransmissions, interface throttling, and, in our case at the end of this article, caching interface data.

Here’s a look at the source code for how Axios implements the interceptor and its adapter ideas.

Interceptor implementation

Interceptors give projects the ability to inject custom behavior at development time. Usually in the request interceptor to achieve a custom request header, in the response interceptor to complete the return data custom code parsing, exception unified processing. Axios’s interceptor consists of three parts: register -> orchestrate -> execute.

Below is the request interceptor registration execution in the project. How is the source code implemented?

Axios. Interceptors. Request. Use (config = > {/ / custom operations, Error =>{return promise.reject (error)})Copy the code

Mount interceptors

The axios.interceptors property mounts the interceptor.

function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}
Copy the code

Task Management Design

The InterceptorManager constructor handles task registration, deregistration, and so on.

function InterceptorManager() { this.handlers = []; } InterceptorManager.prototype.use = function use(fulfilled, rejected){ this.handlers.push({ fulfilled: fulfilled, rejected: rejected }); // Return this.handlers. Length-1; } InterceptorManager.prototype.eject = function eject(id) { if (this.handlers[id]) { this.handlers[id] = null; }};Copy the code

The interceptor instance maintains a Handlers array that holds a list of tasks, each consisting of a pity and Rejected handler functions. You can register a task by calling the use function and return the index value of the current task. An interceptor instance can call the Reject function to empty the tasks corresponding to the index. There are more implementations of interceptors, and you can look at the code for details.

Task scheduling and execution

The interceptor is executed in the REQUEST method of an AXIos instance.

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

Internally, tasks are arranged through chain array. DispatchRequest is a method to initiate URL request encapsulation. When a request interceptor is encountered, its Handlers task list is iterated over and inserted into the head of the chain array. When a response interceptor is encountered, it is added to the end of the choreography array. This is a big pity. Each task is fulfilled according to the audition and rejected in the back.

When there are choreography tasks, they are executed from front to back, so the request interceptor task is executed first, the request is initiated, and the response interceptor is last. When failure occurs, promise.reject () is executed, throwing an error and the task does not proceed further.

Adaptor idea

Axios is widely used in browsers and Node.js servers, where browsers usually initiate requests using XMLHttpRequest or Fetch, while Node servers use Http modules. How is Axios compatible?

The answer is: Adapter Adapter. Objects are identified by variables unique to each environment, such as XMLHttpRequest for the browser, process on the server, and return different adapters.

function getDefaultAdapter() { var adapter; if (typeof XMLHttpRequest ! == 'undefined') { // For browsers use XHR adapter adapter = require('./adapters/xhr'); } else if (typeof process ! == 'undefined') { // For node use HTTP adapter adapter = require('./adapters/http'); } return adapter; }Copy the code
What is an adapter?

Check the lib/adapters/xhr.js file to see that the adapter is just a function that returns a Promise object! Note this, we will customize the extension adapter later! Internal request implementation, not focus on, interested can go to see the source.

module.exports = function xhrAdapter(config) { return new Promise(function dispatchXhrRequest(resolve, Reject) {// Internal URL request implementation}}Copy the code

Cancel duplicate requests

XMLHttpRequest requests can be aborted in the browser. In AXIos, the ability to cancel a request is also provided. Here’s how Axios cancels a request.

const CancelToken = axios.CancelToken; const source = CancelToken.source(); // axios.get('/user/12345', {cancelToken: source.token }).catch(function(thrown) { if (axios.isCancel(thrown)) { console.log('Request canceled', thrown.message); } else {// handle error}}); // Let cancel; axios.get('/user/12345', { cancelToken: New CancelToken(function executor(c) {// The executor function accepts a cancel function as an argument cancel = c; })}); // Cancel the request cancel();Copy the code

We know that Axios can cancel a request by CancelToken, and there are two ways to cancel. So what’s the internal mechanism?

What is CancelToken?

function CancelToken(executor) { if (typeof executor ! == 'function') { throw new TypeError('executor must be a function.'); } var resolvePromise; Promise = new promise (function promiseExecutor(resolve) {resolvePromise = resolve; }); var token = this; executor(function cancel(message) { if (token.reason) { return; } // reason = new Cancel(message); // Reason = new Cancel(message); resolvePromise(token.reason); }); } / / cancellation request execution method CancelToken. Prototype. ThrowIfRequested = function throwIfRequested () {if (this. A tiny) {throw this.reason; }}; CancelToken.source = function source() { var cancel; var token = new CancelToken(function executor(c) {cancel = c; }); return {token: token,cancel: cancel}; };Copy the code

The lesson here is that CancelToken passes in the executor and exposes the internal cancel function as an executor argument. The cancel function here can be executed at some point in the future, and when it is executed the CancelToken instance will be mounted with the Reason attribute of New Cancel. At this point, the current request is cancelled.

How to Cancel a request

// CancelToken.js CancelToken.prototype.throwIfRequested = function throwIfRequested() { if (this.reason) {throw this.reason; }}; / / dispatchRequest. Js internal methods function throwIfCancellationRequested (config) {if (config. CancelToken) { config.cancelToken.throwIfRequested(); } } module.exports = function dispatchRequest(config) { throwIfCancellationRequested(config); // Follow the logic to initiate the request}Copy the code

The cancellation request checks whether cancelToken exists in the configuration and reason exists, that is, whether the cancel function is executed. If there is a reason to cancel the request, the process will not proceed. Either the interceptor or the cancellation of the request is processed before the adapter is triggered, and the business layer does not respond to the underlying request adapter. This is where the AXIos library comes in, strictly stratified.

Example: Cache interface data

Caching interface data with no strict requirements on real-time performance can reduce a network request and make page response more timely. In view of this demand, we can design indicators in advance to make a more robust scheme. Such as:

  1. Interfaces may or may not cache data
  2. Less intrusion into business

From the beauty of model Design section, we know that the actual URL request is initiated in the Adapter adapter. Consider enhancing interception with the default adapter, caching data directly if it exists, and entering the request otherwise. To enhance the benefits of the adapter and to take advantage of axiOS’s extensibility, we can be more flexible by overwriting the adapter globally or changing just a single request adapter. Let’s take a look at the detailed design.

1. Judge the same request

When the URL, parameter, and parameter of the originating request are the same, we can consider them the same request, so we can concatenate the two parameters as the key value of the cache interface data. Take a look at the code:

import buildURL from 'axios/lib/helpers/buildURL'; export default function buildSortedURL(... args: any[]) { const builtURL = buildURL(... args); const [urlPath, queryString] = builtURL.split('? '); if (queryString) { const paramsPair = queryString.split('&'); return `${urlPath}? ${paramsPair.sort().join('&')}`; } return builtURL; }Copy the code

2. Adapter enhancements

Adopting adapter enhancement

export default function cacheAdapterEnhancer(adapter: AxiosAdapter, options: Options = {}): AxiosAdapter {const {// enable default caching enabledByDefault = true, // custom cacheFlag = 'cache', DefaultCache = new LRUCache<string, AxiosPromise>({maxAge: FIVE_MINUTES, Max: CAPACITY}),} = options; return config => { const { url, method, params, paramsSerializer, forceUpdate } = config; Const useCache = ((config as any)[cacheFlag]! == void 0 && (config as any)[cacheFlag] ! == null) ? (config as any)[cacheFlag] : enabledByDefault; // Request if (method === 'get' && useCache) {// If the specified cache is provided, use it const cache: ICacheLike<AxiosPromise> = isCacheLike(useCache) ? useCache : defaultCache; // Build cache key const index = buildSortedURL(URL, params, paramsSerializer); Let responsePromise = cache.get(index); If (forceUpdate = true) if (! responsePromise || forceUpdate) { responsePromise = (async () => { try { return await adapter(config); } catch (reason) { cache.del(index); throw reason; }}) (); Cache.set (index, responsePromise); return responsePromise; } return responsePromise; } // Execute the default adapter return adapter(config); }; }Copy the code

3. Use documentation

  1. Example Configures a custom adapter
Const HTTP = axios.create({baseURL: '/', adapter: cacheAdapterEnhancer(axios.defaults.adapter, {enabledByDefault: false, cacheFlag: 'useCache'}) });Copy the code
  1. Configuration instructions

EnabledByDefault: Enables caching for all requests without specifying it in the request configuration.

CacheFlag: Configure flags to explicitly define cache usage in AXIOS requests.

DefaultCache: By default will be used to store the CacheLike instance of the request, unless a custom Cache is defined using the request configuration.

conclusion

So far we have read the architectural highlights of the Axios project, as well as the idea of implementing the interceptor bottom layer, adaptation, and interface data caching through enhancements to the default adapter. Axios does more than that, of course. Taking a page out of an enhanced adapter, Axios can also implement common requirements such as request throttling, retransmission of failed requests, and cancellation of repeated requests. If you’re interested, check out axios-Extension.