tutorials & docs,tools & experiences for developers

How to Implement an HTTP Request Library with Axios

Overview

In the front-end development process, we often encounter the situations where we need to send asynchronous requests. And a full-featured HTTP request library can greatly reduce our development costs and improve our development efficiency.

Axios is an HTTP request library that has been very hot in recent years. Currently, it has more than 40K stars in GitHub, and it has been recommended by many authority figures.

So, it's necessary to take a look at how axios is designed, and how it helps to implement an HTTP request library. The version of axios was 0.18.0 when writing this article, so let's take this code version as an example to read and analysis specific source code. All current source files for axios are in the lib folder, so the paths below all refer to the paths to the lib folder.

Here we'll mainly talk about:

  • How to use axios
  • How is the core module (requests, interceptors, withdrawals) of axios designed and implemented?
  • What are the advantages of axios design?

How to use axios

To understand the design of axios, first we need to have a look at how to use axios. Let‘s illustrate the following axios API with a simple example.

Send the request

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

This is an official API example. From the above code we can see that the usage of axios is very similar to jQuery's ajax, both of which return a Promise (It can also use the callback of success here, but Promise or await is recommended) to continue the latter operation.

The example is so simple that I needn't explain it. Let's have a look at how to add a filter function.

Add Interceptors function

// Add a request interceptor. Note that there are 2 functions - one succeeds and one fails, and the reason for this will be explained later.
axios.interceptors.request.use(function (config) {
    // The process before sending the request.
    return config;
  }, function (error) {
    // Request error handling.
    return Promise.reject(error);
  });

// Add a response interceptor.
axios.interceptors.response.use(function (response) {
    // Processing for the response data.
    return response;
  }, function (error) {
    // Processing after the response error.
    return Promise.reject(error);
  });

From the above code, we can know: before the request is sent, we can process the data for the requested config parameter; after requesting the response, we can also perform specific operations on the returned data. At the same time, we can make specific error handling when the request or the response fails.

Cancel HTTP request

When developing the search-related modules, we often need to send requests for data query frequently. In general, we need to cancel the last request when we send the next request. Therefore, canceling the function associated with the request is also an advantage. The sample code for axios's canceling request is as follows:

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
  }
});

axios.post('/user/12345', {
  name: 'new name'
}, {
  cancelToken: source.token
})

// cancel the request (the message parameter is optional)
source.cancel('Operation canceled by the user.');

As you can see from the example above, a withdrawal of a proposal based on CancelToken is used in axios. However, the proposal has now been withdrawn, as detailed here. The specific withdrawal implementation method will be explained in the later chapters of source code analysis.

How is the core module of axios designed and implemented?

Through the above example, I believe that everyone has a general understanding of the use of axios. Below, we'll analyze the design and implementation of axios according to the module. The following image is the relevant axios file that we will cover in this blog. If you're interested, you'd better clone the related code when reading, which will deepen the understanding of the relevant modules.

HTTP request module

The code associated with the request module is put in the core/dispatchReqeust.js file. And here I just select some of the key source code for a brief introduction:

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

    // other source code

    // The default adapter is a module that can choose to use Node or XHR for request sending according to 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);
        });
};

In the above code, we can know that the dispatchRequest method is to get the module that sends the request by obtaining config.adapter. We can also replace the original module by passing an adapter function that conforms to the specification (Generally, we won't do so, but it's a loosely coupled extension point).

In the default.js relevant file, we can see the selection logic of the relevant adapter, which will determine according to some unique properties and constructors of the current container.

function getDefaultAdapter() {
    var adapter;
    // Only Node.js has the classes of which variable type is process.
    if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
        // Node.js request module.
        adapter = require('./adapters/http');
    } else if (typeof XMLHttpRequest !== 'undefined') {
        // The browser request module.
        adapter = require('./adapters/xhr');
    }
    return adapter;
}

The XHR module in axios is relatively simple, and it is the encapsulation of the XMLHTTPRequest object. So I don't illustrate it any more here. You can read it yourself if interested. The code is located in the adapters/xhr.js file.

Interceptor module

Now let's see how axios handles request and response Interceptor functions. Let's take a look at the unified interface in axios, which is the request function.

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

    // other 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;
};

This function is the interface for axios to send requests. Because the function implementation is relatively long, I will talk about the related design ideas briefly:

  1. chain is an execution queue. The initial value of the queue is a Promise with the config parameter.
  2. In the chain execution queue, the initial function dispatchReqeust, which is used to send requests, and the function undefined, which is corresponding to the dispatchReqeust,  are inserted. And the reason why it need to add the undefined is that the success and fail callback function are needed in Promise, which also can be seen from the code promise = promise.then(chain.shift(), chain.shift());. Therefore, the function of dispatchReqeust and undefined can be treated as a pair of functions.
  3. In the chain execution queue, the function dispatchReqeust that sends the request is in the middle. It is preceded by a request interceptor, which is inserted by the unshift method; and a response interceptor, which is inserted by the push, will be behind the dispatchReqeust. It should be noted that these functions are put in pairs, which means that they will be put two at a time.

Through the request code above, we roughly know how to use the interceptor. Next, let's see how to cancel an HTTP request.

Cancel-request module

The module related with canceling the request is in the Cancel/ folder. Now let's take a look at the relevant key code.

First, let's have a look at the metadata Cancel class. It is a class used to record the cancellation status. The specific 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;

In the CancelToken class, it implements HTTP request cancellation by passing a Promise method, and the specific code is as follows:

function CancelToken(executor) {
    if (typeof executor !== '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) {
            // Cancellation has already been requested
            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
    };
};

And the corresponding cancel-request code is in the adapter/xhr.js file:

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

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

Through the above example of canceling an HTTP request , let's briefly talk about the relevant implementation logic:

  1. In the request that may need to be canceled, the source method which returns an instance A of the CancelToken class and a function cancel is called for initialization.
  2. When the source method is returning the instance A, a promise in the pending state is initialized. After passing the instance A to axis, the promise is used as a trigger to cancel the request.
  3. When the cancel method returned by the source method is called, the promise state in instance A will be changed from pending to fulfilled, and then the then callback function will be triggered immediately. Thus the cancellation logic of axios - request.abort() is triggered.

What are the advantages of axios design?

Proces logic for sending request functions

As mentioned in the previous chapters, axios doesn't treat the dispatchRequest function, which is used to send the request, as a special function. In fact, the dispatchRequest function will be placed in the middle of the queue to ensure the processing consistency of the queue and improve the readability of the code.

Process logic for adapter

In the processing logic of the adapter, the http and xhr modules (one for Node.js to send requests, and the other for browsers to send requests) are not used in the dispatchRequest as its own module directly, but introduced in the default.js file through the configured method by default. Thus it not only ensures low coupling between the two modules, but also leaves room for future users to customize request sending modules.

Process logic for cancelling HTTP requests

In the logic to cancel the HTTP request, axios is designed to use a Promise as a trigger, passing the resolve function to the outside as a parameter in the callback. It not only ensures the consistency of the internal logic, but also ensures that when the cancellation request is required, it's not necessary to change the sample data of the related class directly in order to avoid invading other modules to the most extent.

Summary

This article gives us a detailed introduction to axios usage, design ideas and implementation methods. After reading, you can know the design of axios, and learn about the module encapsulation and interaction.

It only introduces the core module of axios in this article. And if you are interested in other code, you can go to GitHub to view it.

If you have any questions or opinions, please feel free to leave a message.

0 Comment

temp