HTTP请求库——axios源码阅读与分析

概述

在前端开发过程中,我们经常会遇到需要发送异步请求的情况。而使用一个功能齐全,接口完善的HTTP请求库,能够在很大程度上减少我们的开发成本,提高我们的开发效率。

axios是一个在近些年来非常火的一个HTTP请求库,目前在GitHub中已经拥有了超过40K的star,受到了各位大佬的推荐。

今天,我们就来看下,axios到底是如何设计的,其中又有哪些值得我们学习的地方。我在写这边文章时,axios的版本为0.18.0。我们就以这个版本的代码为例,来进行具体的源码阅读和分析。当前axios所有源码文件都在 lib文件夹中,因此我们下文中提到的路径均是指 lib文件夹中的路径。

本文的主要内容有:

  • 如何使用axios
  • axios的核心模块是如何设计与实现的(请求、拦截器、撤回)
  • axios的设计有什么值得借鉴的地方

如何使用axios

想要了解axios的设计,我们首先需要来看下axios是如何使用的。我们通过一个简单示例来介绍以下axios的API。

发送请求

 1 axios({
 2
 3   method:‘get‘,
 4
 5   url:‘http://bit.ly/2mTM3nY‘,
 6
 7   responseType:‘stream‘
 8
 9 })
10
11   .then(function(response) {
12
13   response.data.pipe(fs.createWriteStream(‘ada_lovelace.jpg‘))
14
15 });

这是一个官方的API示例。从上面的代码中我们可以看到,axios的用法与jQuery的ajax很相似,都是通过返回一个Promise(也可以通过success的callback,不过建议使用Promise或者await)来继续后面的操作。

这个代码示例很简单,我就不过多赘述了,下面让我们来看下如何添加一个过滤器函数。

增加拦截器(Interceptors)函数

 1 // 增加一个请求拦截器,注意是2个函数,一个处理成功,一个处理失败,后面会说明这种情况的原因
 2
 3 axios.interceptors.request.use(function (config) {
 4
 5     // 请求发送前处理
 6
 7     return config;
 8
 9   }, function (error) {
10
11     // 请求错误后处理
12
13     return Promise.reject(error);
14
15   });
16
17
18 // 增加一个响应拦截器
19
20 axios.interceptors.response.use(function (response) {
21
22     // 针对响应数据进行处理
23
24     return response;
25
26   }, function (error) {
27
28     // 响应错误后处理
29
30     return Promise.reject(error);
31
32   });

通过上面的示例我们可以知道:在请求发送前,我们可以针对请求的config参数进行数据处理;而在请求响应后,我们也能针对返回的数据进行特定的操作。同时,在请求失败和响应失败时,我们都可以进行特定的错误处理。

取消HTTP请求

在完成搜索相关的功能时,我们经常会需要频繁的发送请求来进行数据查询的情况。通常来说,我们在下一次请求发送时,就需要取消上一次请求。因此,取消请求相关的功能也是一个优点。axios取消请求的示例代码如下:

 1 const CancelToken = axios.CancelToken;
 2
 3 const source = CancelToken.source();
 4
 5
 6 axios.get(‘/user/12345‘, {
 7
 8   cancelToken: source.token
 9
10 }).catch(function(thrown) {
11
12   if (axios.isCancel(thrown)) {
13
14     console.log(‘Request canceled‘, thrown.message);
15
16   } else {
17
18     // handle error
19
20   }
21
22 });
23
24
25 axios.post(‘/user/12345‘, {
26
27   name: ‘new name‘
28
29 }, {
30
31   cancelToken: source.token
32
33 })
34
35
36 // cancel the request (the message parameter is optional)
37
38 source.cancel(‘Operation canceled by the user.‘);

通过上面的示例我们可以看到,axios使用的是基于CancelToken的一个撤回提案。不过,目前该提案已经被撤回,具体详情可以见此处。具体的撤回实现方法我们会在后面的章节源码分析的时候进行说明。

axios的核心模块是如何设计与实现的

通过上面的例子,我相信大家对axios的使用方法都有了一个大致的了解。下面,我们将按照模块来对axios的设计与实现进行分析。下图是我们在这篇博客中将会涉及到的相关的axios的文件,如果读者有兴趣的话,可以通过clone相关代码结合博客进行阅读,这样能够加深对相关模块的理解。

HTTP请求模块

作为核心模块,axios发送请求相关的代码位于 core/dispatchReqeust.js文件中。由于篇幅有限,下面我选取部分重点的源码进行简单的介绍:

 1 module.exports = function dispatchRequest(config) {
 2
 3     throwIfCancellationRequested(config);
 4
 5
 6     // 其他源码
 7
 8
 9     // default adapter是一个可以判断当前环境来选择使用Node还是XHR进行请求发送的模块
10
11     var adapter = config.adapter || defaults.adapter;
12
13
14     return adapter(config).then(function onAdapterResolution(response) {
15
16         throwIfCancellationRequested(config);
17
18
19         // 其他源码
20
21
22         return response;
23
24     }, function onAdapterRejection(reason) {
25
26         if (!isCancel(reason)) {
27
28             throwIfCancellationRequested(config);
29
30
31             // 其他源码
32
33
34             return Promise.reject(reason);
35
36         });
37
38 };

通过上面的代码和示例我们可以知道, dispatchRequest方法是通过获取 config.adapter来得到发送请求的模块的,我们自己也可以通过传入符合规范的adapter函数来替换掉原生的模块(虽然一般不会这么做,不过也算是一个松耦合扩展点)。

在 default.js文件中,我们能够看到相关的adapter选择逻辑,即根据当前容器中特有的一些属性和构造函数来进行判断。

 1 function getDefaultAdapter() {
 2
 3     var adapter;
 4
 5     // 只有Node.js才有变量类型为process的类
 6
 7     if (typeof process !== ‘undefined‘ && Object.prototype.toString.call(process) === ‘[object process]‘) {
 8
 9         // Node.js请求模块
10
11         adapter = require(‘./adapters/http‘);
12
13     } else if (typeof XMLHttpRequest !== ‘undefined‘) {
14
15         // 浏览器请求模块
16
17         adapter = require(‘./adapters/xhr‘);
18
19     }
20
21     return adapter;
22
23 }

axios中XHR模块较为简单,为XMLHTTPRequest对象的封装,我们在这里就不过多进行介绍了,有兴趣的同学可以自行阅读,代码位于 adapters/xhr.js文件中。

拦截器模块

了解了 dispatchRequest实现的HTTP请求发送模块,我们来看下axios是如何处理请求和响应拦截函数的。让我们看下axios中请求的统一入口 request函数。

 1 Axios.prototype.request = function request(config) {
 2
 3
 4     // 其他代码
 5
 6
 7     var chain = [dispatchRequest, undefined];
 8
 9     var promise = Promise.resolve(config);
10
11
12     this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
13
14         chain.unshift(interceptor.fulfilled, interceptor.rejected);
15
16     });
17
18
19     this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
20
21         chain.push(interceptor.fulfilled, interceptor.rejected);
22
23     });
24
25
26     while (chain.length) {
27
28         promise = promise.then(chain.shift(), chain.shift());
29
30     }
31
32
33     return promise;
34
35 };

这个函数是axios发送请求的入口,因为函数实现比较长,我就简单说一下相关的设计思路:

  1. chain是一个执行队列。这个队列的初始值,是一个带有config参数的Promise。
  2. 在chain执行队列中,插入了初始的发送请求的函数 dispatchReqeust和与之对应的undefined。后面需要增加一个 undefined是因为在Promise中,需要一个success和一个fail的回调函数,这个从代码 promise = promise.then(chain.shift(),chain.shift());就能够看出来。因此, dispatchReqeust和 undefined我们可以成为一对函数。
  3. 在chain执行队列中,发送请求的函数 dispatchReqeust是处于中间的位置。它的前面是请求拦截器,通过 unshift方法放入;它的后面是响应拦截器,通过 push放入。要注意的是,这些函数都是成对的放入,也就是一次放入两个。

通过上面的 request代码,我们大致知道了拦截器的使用方法。接下来,我们来看下如何取消一个HTTP请求。

取消请求模块

取消请求相关的模块在 Cancel/文件夹中。让我们来看下相关的重点代码。

首先,让我们来看下元数据 Cancel类。它是用来记录取消状态一个类,具体代码如下:

 1  function Cancel(message) {
 2
 3       this.message = message;
 4
 5     }
 6
 7
 8     Cancel.prototype.toString = function toString() {
 9
10       return ‘Cancel‘ + (this.message ? ‘: ‘ + this.message : ‘‘);
11
12     };
13
14
15     Cancel.prototype.__CANCEL__ = true;

而在CancelToken类中,它通过传递一个Promise的方法来实现了HTTP请求取消,然我们看下具体的代码:

 1 function CancelToken(executor) {
 2
 3     if (typeof executor !== ‘function‘) {
 4
 5         throw new TypeError(‘executor must be a function.‘);
 6
 7     }
 8
 9
10     var resolvePromise;
11
12     this.promise = new Promise(function promiseExecutor(resolve) {
13
14         resolvePromise = resolve;
15
16     });
17
18
19     var token = this;
20
21     executor(function cancel(message) {
22
23         if (token.reason) {
24
25             // Cancellation has already been requested
26
27             return;
28
29         }
30
31
32         token.reason = new Cancel(message);
33
34         resolvePromise(token.reason);
35
36     });
37
38 }
39
40
41 CancelToken.source = function source() {
42
43     var cancel;
44
45     var token = new CancelToken(function executor(c) {
46
47         cancel = c;
48
49     });
50
51     return {
52
53         token: token,
54
55         cancel: cancel
56
57     };
58
59 };

而在 adapter/xhr.js文件中,有与之相对应的取消请求的代码:

 1 if (config.cancelToken) {
 2
 3     // 等待取消
 4
 5     config.cancelToken.promise.then(function onCanceled(cancel) {
 6
 7         if (!request) {
 8
 9             return;
10
11         }
12
13
14         request.abort();
15
16         reject(cancel);
17
18         // 重置请求
19
20         request = null;
21
22     });
23
24 }

结合上面的取消HTTP请求的示例和这些代码,我们来简单说下相关的实现逻辑:

  1. 在可能需要取消的请求中,我们初始化时调用了source方法,这个方法返回了一个CancelToken类的实例A和一个函数cancel。
  2. 在source方法返回实例A中,初始化了一个在pending状态的promise。我们将整个实例A传递给axios后,这个promise被用于做取消请求的触发器。
  3. 当source方法返回的cancel方法被调用时,实例A中的promise状态由pending变成了fulfilled,立刻触发了then的回调函数,从而触发了axios的取消逻辑——request.abort()

axios的设计有什么值得借鉴的地方

发送请求函数的处理逻辑

在之前的章节中有提到过,axios在处理发送请求的 dispatchRequest函数时,没有当做一个特殊的函数来对待,而是采用一视同仁的方法,将其放在队列的中间位置,从而保证了队列处理的一致性,提高了代码的可阅读性。

Adapter的处理逻辑

在adapter的处理逻辑中,axios没有把http和xhr两个模块(一个用于Node.js发送请求,另一个则用于浏览器端发送请求)当成自身的模块直接在 dispatchRequest中直接饮用,而是通过配置的方法在 default.js文件中进行默认引入。这样既保证了两个模块间的低耦合性,同时又能够为今后用户需要自定义请求发送模块保留了余地。

取消HTTP请求的处理逻辑

在取消HTTP请求的逻辑中,axios巧妙的使用了一个Promise来作为触发器,将resolve函数通过callback中参数的形式传递到了外部。这样既能够保证内部逻辑的连贯性,也能够保证在需要进行取消请求时,不需要直接进行相关类的示例数据改动,最大程度上避免了侵入其他的模块。

总结

本文对axios相关的使用方式、设计思路和实现方法进行了详细的介绍。读者能够通过上述文章,了解axios的设计思想,同时能够在axios的代码中,学习到关于模块封装和交互等相关的经验。

由于篇幅原因,本文仅针对axios的核心模块进行了分解和介绍,如果对其他代码有兴趣的同学,可以去GitHub进行查看。

如果有任何疑问或者观点,欢迎随时留言讨论。

作者:hjava

原文:https://segmentfault.com/a/1190000015747143

原文地址:https://www.cnblogs.com/leungUwah/p/9398975.html

时间: 2024-11-10 16:12:50

HTTP请求库——axios源码阅读与分析的相关文章

CopyOnWriteArrayList 源码阅读与分析

CopyOnWriteArrayList 源码阅读与分析 CopyOnWriteArrayList是并发包下的一个线程安全.可以实现高并发的ArrayList 首先来看看它的构造方法: final void setArray(Object[] a) { array = a; } /** * Creates an empty list. */ public CopyOnWriteArrayList() { setArray(new Object[0]); } 可以看到,它和ArrayList有一定

ArrayBlockingQueue 源码阅读与分析

ArrayBlockingQueue 源码阅读与分析 通过这个类的名字,可以知道ArrayBlockingQueue是一个底层使用数组实现,具有队列特点的先进先出以及线程安全的一个集合类,他还可以实现指定时间的阻塞读写,也就是可以解决生产者消费者问题的阻塞队列. 首先来看一下它的构造方法: public ArrayBlockingQueue(int capacity) { this(capacity, false); } public ArrayBlockingQueue(int capacit

源码阅读与分析一:利用eclipse查看ssh等源码

要阅读开源框架代码,阅读class文件,我们一般有两种方法 1.利用maven进行相关操作 2.下载jar包的源码包进行导入阅读 这里我采用的是第二种 具体步骤如下: 一:这里如果我们要阅读struts源码,首先到官方下载,这里推荐下载all包,就是包含docs,src,lib的包,当然你也可以只下载lib和src包,lib包为我们的jar包,就是平常需要导入项目的,src包为jar包的源码包,里面包含所有jar包的.java源文件,docs则是我们的文档,里面包含struts的使用说明文档 首

Spring源码阅读:Spring MVC 如何处理HTTP请求

Spring MVC 对HTTP请求的处理流程 通过之前的源码阅读,知道了ApplicationContext初始的过程,也知道了Spring MVC环境的初始化过程,今天就来了解一下SpringMVC是如何处理HTTP请求的. HTTP请求根据请求方式可以分为GET.POST.PUT.DELETE.OPTIONS.TRACE,最常用的还是GET和POST. Spring对于这几种HTTP请求的处理都是使用了processRequest(req,rep); @Override protected

【原】SDWebImage源码阅读(三)

[原]SDWebImage源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1.SDWebImageDownloader中的downloadImageWithURL 我们来到SDWebImageDownloader.m文件中,找到downloadImageWithURL函数.发现代码不是很长,那就一行行读.毕竟这个函数大概做什么我们是知道的.这个函数大概就是创建了一个SDWebImageSownloader的异步下载器,根据给定的URL下载image. 先映入眼帘的

【原】SDWebImage源码阅读(一)

[原]SDWebImage源码阅读(一) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 一直没有系统地读过整套源码,就感觉像一直看零碎的知识点,没有系统读过一本专业经典书籍一样,会有点发虚,感觉知识体系不健全!废话少说,这次我决定好好阅读下SDWebImage的源码,我的阅读方式,是带着问题去阅读源码,然后强迫自己写博客. 2. SDWebImage是做什么的? 既然是要带着问题读,那么第一个问题就来了,SDWebImage是做什么的?SDWebImage是一个开源

【 js 基础 】【 源码学习 】backbone 源码阅读(一)

最近看完了 backbone.js 的源码,这里对于源码的细节就不再赘述了,大家可以 star 我的源码阅读项目(https://github.com/JiayiLi/source-code-study)进行参考交流,有详细的源码注释,以及知识总结,同时 google 一下 backbone 源码,也有很多优秀的文章可以用来学习. 我这里主要记录一些偏设计方向的知识点.具体从以下几个方面入手:1.MVC 框架2.观察者模式 以及 控制反转 一.MVC 框架所谓 MVC 框架,包含三个部分,mod

koa源码阅读[0]

koa源码阅读[0] Node.js也是写了两三年的时间了,刚开始学习Node的时候,hello world就是创建一个HttpServer,后来在工作中也是经历过Express.Koa1.x.Koa2.x以及最近还在研究的结合着TypeScript的routing-controllers(驱动依然是Express与Koa).用的比较多的还是Koa版本,也是对它的洋葱模型比较感兴趣,所以最近抽出时间来阅读其源码,正好近期可能会对一个Express项目进行重构,将其重构为koa2.x版本的,所以,

koa源码阅读[2]-koa-router

koa源码阅读[2]-koa-router 第三篇,有关koa生态中比较重要的一个中间件:koa-router 第一篇:koa源码阅读-0第二篇:koa源码阅读-1-koa与koa-compose koa-router是什么 首先,因为koa是一个管理中间件的平台,而注册一个中间件使用use来执行.无论是什么请求,都会将所有的中间件执行一遍(如果没有中途结束的话)所以,这就会让开发者很困扰,如果我们要做路由该怎么写逻辑? app.use(ctx => { switch (ctx.url) { c