The content shared with you in this article is about how to implement an HTTP request library through axios source code analysis. The content is very detailed. Next, let’s take a look at the specific content. I hope it can help everyone.
During the front-end development process, we often encounter situations where we need to send asynchronous requests. Using a HTTP request library with complete functions and complete interfaces can reduce our development costs to a great extent and improve our development efficiency.
axios is an HTTP request library that has become very popular in recent years. It currently has more than 40K stars in GitHub and has been recommended by everyone.
Today, let’s take a look at how axios is designed and what is worth learning from it. When I wrote this article, the version of axios was 0.18.0. Let’s take this version of the code as an example to conduct specific source code reading and analysis. Currently all axios source code files are in the lib
folder, so the paths we mention below refer to the paths in the lib
folder.
The main contents of this article are:
How to use axios
How the core module of axios is designed and implemented ( Request, interceptor, withdrawal)
What is worth learning from the design of axios
To understand the design of axios, we first need to look at how axios is used. We introduce the following axios API through a simple example.
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 continue subsequent operations by returning a Promise (you can also use the success callback, but it is recommended to use Promise or await).
This code example is very simple, so I won’t go into details. Let’s take a look at how to add a filter function.
// 增加一个请求拦截器,注意是2个函数,一个处理成功,一个处理失败,后面会说明这种情况的原因 axios.interceptors.request.use(function (config) { // 请求发送前处理 return config; }, function (error) { // 请求错误后处理 return Promise.reject(error); }); // 增加一个响应拦截器 axios.interceptors.response.use(function (response) { // 针对响应数据进行处理 return response; }, function (error) { // 响应错误后处理 return Promise.reject(error); });
Through the above example we can know: before the request is sent, we can perform data processing on the config parameters of the request; After requesting a response, we can also perform specific operations on the returned data. At the same time, we can perform specific error handling when the request fails and the response fails.
When completing search-related functions, we often need to send frequent requests for data query. Generally speaking, when we send the next request, we need to cancel the previous request. Therefore, the ability to cancel requests is also a plus. The sample code for axios cancellation 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.');
From the above example, we can see that axios uses a withdrawal proposal based on CancelToken. However, the proposal has now been withdrawn. Details can be found here. We will explain the specific withdrawal implementation method in the source code analysis in the following chapters.
Through the above examples, I believe everyone has a general understanding of how to use axios. Next, we will analyze the design and implementation of axios according to modules. The picture below shows the relevant axios files that we will cover in this blog. If readers are interested, they can read them through the clone related code in combination with the blog, which can deepen their understanding of the relevant modules.
As the core module, the code related to axios sending requests is located in the core/dispatchReqeust.js
file. Due to limited space, I will select some key source codes for a brief introduction below:
module.exports = function dispatchRequest(config) { throwIfCancellationRequested(config); // 其他源码 // default adapter是一个可以判断当前环境来选择使用Node还是XHR进行请求发送的模块 var adapter = config.adapter || defaults.adapter; return adapter(config).then(function onAdapterResolution(response) { throwIfCancellationRequested(config); // 其他源码 return response; }, function onAdapterRejection(reason) { if (!isCancel(reason)) { throwIfCancellationRequested(config); // 其他源码 return Promise.reject(reason); }); };
From the above code and examples, we can know that the dispatchRequest
method is to obtain config.adapter
To get the module that sends the request, we can also replace the native module by passing in the adapter function that conforms to the specification (although this is generally not done, it can be regarded as a loosely coupled extension point).
In the default.js
file, we can see the relevant adapter selection logic, which is based on some properties and constructors unique to the current container.
function getDefaultAdapter() { var adapter; // 只有Node.js才有变量类型为process的类 if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') { // Node.js请求模块 adapter = require('./adapters/http'); } else if (typeof XMLHttpRequest !== 'undefined') { // 浏览器请求模块 adapter = require('./adapters/xhr'); } return adapter; }
The XHR module in axios is relatively simple. It is an encapsulation of the XMLHTTPRequest object. We will not introduce it in detail here. Interested students can read it by themselves. The code is located at adapters/xhr.js
in the file.
Understanding the HTTP request sending module implemented by dispatchRequest
, let’s take a look at how axios handles the request and response interception functions of. Let's take a look at the unified entry request
function requested in axios.
Axios.prototype.request = 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; };
This function is the entry point for axios to send requests. Because the function implementation is relatively long, I will briefly talk about the relevant design ideas:
chain是一个执行队列。这个队列的初始值,是一个带有config参数的Promise。
在chain执行队列中,插入了初始的发送请求的函数dispatchReqeust
和与之对应的undefined
。后面需要增加一个undefined
是因为在Promise中,需要一个success和一个fail的回调函数,这个从代码promise = promise.then(chain.shift(), chain.shift());
就能够看出来。因此,dispatchReqeust
和undefined
我们可以成为一对函数。
在chain执行队列中,发送请求的函数dispatchReqeust
是处于中间的位置。它的前面是请求拦截器,通过unshift
方法放入;它的后面是响应拦截器,通过push
放入。要注意的是,这些函数都是成对的放入,也就是一次放入两个。
通过上面的request
代码,我们大致知道了拦截器的使用方法。接下来,我们来看下如何取消一个HTTP请求。
取消请求相关的模块在Cancel/
文件夹中。让我们来看下相关的重点代码。
首先,让我们来看下元数据Cancel
类。它是用来记录取消状态一个类,具体代码如下:
function Cancel(message) { this.message = message; } Cancel.prototype.toString = function toString() { return 'Cancel' + (this.message ? ': ' + this.message : ''); }; Cancel.prototype.__CANCEL__ = true;
而在CancelToken类中,它通过传递一个Promise的方法来实现了HTTP请求取消,然我们看下具体的代码:
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 }; };
而在adapter/xhr.js
文件中,有与之相对应的取消请求的代码:
if (config.cancelToken) { // 等待取消 config.cancelToken.promise.then(function onCanceled(cancel) { if (!request) { return; } request.abort(); reject(cancel); // 重置请求 request = null; }); }
结合上面的取消HTTP请求的示例和这些代码,我们来简单说下相关的实现逻辑:
在可能需要取消的请求中,我们初始化时调用了source方法,这个方法返回了一个CancelToken
类的实例A和一个函数cancel。
在source方法返回实例A中,初始化了一个在pending状态的promise。我们将整个实例A传递给axios后,这个promise被用于做取消请求的触发器。
当source方法返回的cancel方法被调用时,实例A中的promise状态由pending变成了fulfilled,立刻触发了then的回调函数,从而触发了axios的取消逻辑——request.abort()
。
在之前的章节中有提到过,axios在处理发送请求的dispatchRequest
函数时,没有当做一个特殊的函数来对待,而是采用一视同仁的方法,将其放在队列的中间位置,从而保证了队列处理的一致性,提高了代码的可阅读性。
在adapter的处理逻辑中,axios没有把http和xhr两个模块(一个用于Node.js发送请求,另一个则用于浏览器端发送请求)当成自身的模块直接在dispatchRequest
中直接饮用,而是通过配置的方法在default.js
文件中进行默认引入。这样既保证了两个模块间的低耦合性,同时又能够为今后用户需要自定义请求发送模块保留了余地。
在取消HTTP请求的逻辑中,axios巧妙的使用了一个Promise来作为触发器,将resolve函数通过callback中参数的形式传递到了外部。这样既能够保证内部逻辑的连贯性,也能够保证在需要进行取消请求时,不需要直接进行相关类的示例数据改动,最大程度上避免了侵入其他的模块。
本文对axios相关的使用方式、设计思路和实现方法进行了详细的介绍。读者能够通过上述文章,了解axios的设计思想,同时能够在axios的代码中,学习到关于模块封装和交互等相关的经验。
由于篇幅原因,本文仅针对axios的核心模块进行了分解和介绍,如果对其他代码有兴趣的同学,可以去GitHub进行查看。
相关推荐:
The above is the detailed content of Axios source code analysis how to implement an HTTP request library. For more information, please follow other related articles on the PHP Chinese website!