How to Implement an HTTP Request Library with Axios
- ·
- 10 Aug 2018
- ·
- ·
- 15179 Views
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:
- chain is an execution queue. The initial value of the queue is a Promise with the config parameter.
- In the chain execution queue, the initial function
dispatchReqeust
, which is used to send requests, and the functionundefined
, which is corresponding to thedispatchReqeust
, are inserted. And the reason why it need to add theundefined
is that the success and fail callback function are needed in Promise, which also can be seen from the codepromise = promise.then(chain.shift(), chain.shift());
. Therefore, the function ofdispatchReqeust
andundefined
can be treated as a pair of functions. - In the
chain
execution queue, the functiondispatchReqeust
that sends the request is in the middle. It is preceded by a request interceptor, which is inserted by theunshift
method; and a response interceptor, which is inserted by thepush
, will be behind thedispatchReqeust
. 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:
- In the request that may need to be canceled, the
source
method which returns an instance A of theCancelToken
class and a functioncancel
is called for initialization. - When the
source
method is returning the instance A, apromise
in thepending
state is initialized. After passing the instance A to axis, thepromise
is used as a trigger to cancel the request. - When the
cancel
method returned by the source method is called, thepromise
state in instance A will be changed frompending
tofulfilled
, and then thethen
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
Login to post a comment
Login With Github