Originally posted on TutorialDocs, How to Implement an HTTP Request Library with Axios.

An overview of the

Sending asynchronous requests is a common scenario in front-end development. A full-featured HTTP request library can greatly reduce our development costs and improve development efficiency.

Axios is one such HTTP request library that has become very popular in recent years. It currently has over 40,000 stars on GitHub and is recommended by many pundits.

Therefore, it is important to understand how AXIOS is designed and how HTTP request library encapsulation is implemented. As of this writing, the current version of Axios is 0.18.0, which is used as an example to read and analyze some of the core source code. All axios source files are located in the lib folder, and the paths mentioned below are relative to lib.

In this paper, we mainly discuss:

  • How to use Axios.

  • How are axios’s core modules (request, interceptor, undo) designed and implemented?

  • What are axios’s design strengths?

How do I use Axios

To understand axios’s design, you first need to look at how to use Axios. Let’s take a simple example to illustrate the use of the AXIos API.

Send the request

axios({
  method:'get'.url:'http://bit.ly/2mTM3nY'.responseType:'stream'
})
  .then(function(response) {
  response.data.pipe(fs.createWriteStream('ada_lovelace.jpg'))});Copy the code

This is an official example. As you can see from the code above, axios is used very similar to jQuery’s Ajax methods, in that both return a Promise object (you can use the success callback here, too, but Promise or await is still preferred), and then proceed.

This example is so simple that I don’t need to explain it. Let’s see how to add an interceptor function.

Add interceptor functions

// Add a request interceptor. Note that there are two functions -- a callback on success and a callback on failure -- for reasons that will be explained later
axios.interceptors.request.use(function (config) {
    // Perform some processing tasks before initiating the request
    return config; // Returns the configuration information
  }, function (error) {
    // Request error handling
    return Promise.reject(error);
  });

// Add a response interceptor
axios.interceptors.response.use(function (response) {
    // Process the response data
    return response; // Return the response data
  }, function (error) {
    // The processing done after the response error
    return Promise.reject(error);
  });
Copy the code

From the above code, we can know that before sending the request, we can do the request configuration parameter (config); After the request receives a response, we can process the returned data. We can also specify the corresponding error handler when the request or response fails.

Revoking the HTTP request

When developing search-related modules, we often send data query requests frequently. In general, when we send the next request, we need to undo the previous request. Therefore, the ability to undo related requests is very useful. Example code for axios undo request is as follows:

const CancelToken = axios.CancelToken;
const source = CancelToken.source();

A / / examples
axios.get('/user/12345', {
  cancelToken: source.token
}).catch(function(thrown) {
  if (axios.isCancel(thrown)) {
    console.log('Request withdrawn', thrown.message);
  } else {
    // Processing error}});2 / / examples
axios.post('/user/12345', {
  name: 'New name'
}, {
  cancelToken: source.token
}).

// Undo the request (info parameter is optional)
source.cancel('User has withdrawn the request');
Copy the code

As you can see from the above example, in AXIos, a CancelToken based undo request scheme is used. However, the proposal has now been withdrawn, as detailed here. The implementation of the undo request will be explained later in the source code analysis.

Design and implementation of AXIOS core modules

From the example above, I’m sure everyone has an idea of how axios works. Below, we examine the design and implementation of AXIOS in terms of modules. The following image is the source file I will cover in this article. If you are interested, it is best to clone the relevant code as you read. This will deepen your understanding of the relevant modules.

HTTP request module

The request module’s code is in the core/dispatchRequest.js file, and I’ve just shown some key code here to illustrate it briefly:

module.exports = function dispatchRequest(config) {
    throwIfCancellationRequested(config);

    // Other source code

    // The default adapter is a module that can send requests using Node or XHR depending on the current environment.
    var adapter = config.adapter || defaults.adapter; 

    return adapter(config).then(function onAdapterResolution(response) {
        throwIfCancellationRequested(config);

        // Other source code

        return response;
    }, function onAdapterRejection(reason) {
        if(! isCancel(reason)) { throwIfCancellationRequested(config);// Other source code

            return Promise.reject(reason);
        });
};
Copy the code

In the above code, we can see that the dispatchRequest method gets the send request module through config.adapter. We can also replace the original module by passing in a canonical adapter function (generally, we don’t do this, but it’s a loosely coupled extension point).

In the defaults.js file, you can see the selection logic for the associated adapter — which adapter to use, based on some unique properties and constructors of the current container.

function getDefaultAdapter() {
    var adapter;
    Node.js uses its request module only if it contains an object of type Process
    if (typeofprocess ! = ='undefined' && Object.prototype.toString.call(process) === '[object process]') {
        // node.js request module
        adapter = require('./adapters/http');
    } else if (typeofXMLHttpRequest ! = ='undefined') {
        // Browser request module
        adapter = require('./adapters/xhr');
    }
    return adapter;
}
Copy the code

The XHR module in AXIos is a relatively simple wrapper around the XMLHTTPRequest object, which I won’t explain here. If you are interested, check the source code for yourself. The source code is in the Adapters /xhr.js file.

Interceptor module

Now let’s look at how AXIos handles request and response interceptor functions. This brings us to the unified interface in AXIOS, the request function.

Axios.prototype.request = function request(config) {

    // Other source code

    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

This function is the interface through which AXIos sends the request. Because the function implementation code is quite long, I will briefly discuss related design ideas here:

  1. A chain is an execution queue. The initial value of the queue is a Promise object with a config parameter.

  2. In the execution queue, the initial function dispatchRequest is used to send the request. To correspond with dispatchRequest, we add undefined. The reason for adding undefined is to provide success and failure callbacks to Promise, from the following Promise = promise.then(chain.shift(), chain.shift()); We can see that. Therefore, the dispatchRequest and undefiend functions can be regarded as a pair of functions.

  3. In the execution queue chain, the dispatchReqeust function that sends the request is in the middle. It is preceded by a request interceptor, inserted using the unshift method; It is followed by the response interceptor, inserted using the push method, after dispatchRequest. Note that these functions are paired, meaning that two are inserted at once.

Looking at the request function code above, we have a rough idea of how to use interceptors. Next, let’s see how to undo an HTTP request.

Undo request module

The module associated with the Cancel request is located under the Cancel/ folder, so let’s take a look at the core code.

First, let’s look at the base Cancel class. It is a class used to record the undo status, the code is as follows:

function Cancel(message) {
  this.message = message;
}

Cancel.prototype.toString = function toString() {
  return 'Cancel' + (this.message ? ':' + this.message : ' ');
};

Cancel.prototype.__CANCEL__ = true;
Copy the code

When you use the CancelToken class, you pass it a Promise method that cancels the HTTP request as follows:

function CancelToken(executor) {
    if (typeofexecutor ! = ='function') {
        throw new TypeError('executor must be a function.');
    }

    var resolvePromise;
    this.promise = new Promise(function promiseExecutor(resolve) {
        resolvePromise = resolve;
    });

    var token = this;
    executor(function cancel(message) {
        if (token.reason) {
            // It has been cancelled
            return;
        }

        token.reason = new Cancel(message);
        resolvePromise(token.reason);
    });
}

CancelToken.source = function source() {
    var cancel;
    var token = new CancelToken(function executor(c) {
        cancel = c;
    });
    return {
        token: token,
        cancel: cancel
    };
};
Copy the code

In the Adapters /xhr.js file, the area for the cancellation request reads:

if (config.cancelToken) {
    // Wait for undo
    config.cancelToken.promise.then(function onCanceled(cancel) {
        if(! request) {return;
        }

        request.abort();
        reject(cancel);
        // Reset the request
        request = null;
    });
}
Copy the code

Using the HTTP request cancellation example above, let’s briefly discuss the relevant implementation logic:

  1. On the request to cancel, the source method class of the CancelToken class is called to initialize and an object containing the CancelToken class instance A and the Cancel method is returned.

  2. When the Source method is returning instance A, A promise object in its pending state is initialized. After instance A is passed to AXIOS, the Promise can be used as A trigger to undo the request.

  3. When the Cancel method returned by the Source method is called, the promise state in instance A changes from Pending to depressing, and the then callback is immediately triggered. So axios’s undo method — Request.abort () is fired.

What are the benefits of axios doing this?

Send the processing logic of the request function

As discussed in the previous chapters, AXIos does not treat the dispatchRequest function used to send the request as a special function. In practice, dispatchRequest is placed in the middle of the queue to ensure consistent queue processing and readability of the code.

The processing logic of the adapter

The HTTP and XHR modules (one for sending requests in Node.js and one for sending requests in the browser) are not used in the dispatchRequest function, but as separate modules. This is introduced by default through the configuration method in the defaults.js file. Thus, it not only ensures low coupling between the two modules, but also provides room for future users to customize the request sending module.

Undo HTTP request logic

Axios designs to use Promise as a trigger in the undo HTTP request logic, exposing the resolve function to use in the callback function. Not only does it ensure consistency of internal logic, but it also ensures that when a request needs to be revoked, there is no need to directly change the sample data of the relevant classes to avoid a significant intrusion into other modules.

conclusion

This article introduces axiOS usage, design idea and implementation method in detail. After reading this, you can understand the design of AXIos and understand the encapsulation and interaction of modules.

This article covers only the core axios modules, but if you’re interested in the rest of the module code, you can check it out on GitHub.

(after)