贰:Interceptor拦截器和Axios类

接下来,我们应该看一下Axios类中究竟做了什么,到底是怎么发送请求的。因为Axios类中应用到了Interceptor,所以我们要先看一下拦截器是怎么实现的。

很遗憾,至于发送请求的详细内容需要到dispatchRequest方法和xhr适配器中才能看到了,在本章节Axios类中我们只能说到调用dispatchRequest方法这一步,所以发送请求的详细内容要留到下一章节来说了,先来看一下本章节的内容吧。

InterceptorManager

先来看一下拦截器内容,源码位于lib/core/InterceptorManager.js

构造函数

构造函数的内容太简单了,直接放下边吧:

1
2
3
function InterceptorManager() {
this.handlers = [];
}

就是维护了一个handlers数组,用来收集拦截器。

原型方法

原型对象上有useejectforEach三个方法,其中最常用的就是use方法。

  • InterceptorManager.prototype.use

函数接收两个参数,分别是成功和失败的回调函数,最后函数会返回最后加入hadnlers中的拦截器的下标

1
2
3
4
5
6
7
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
});
return this.handlers.length - 1;
};

至于fulfilledrejected函数应该做哪些内容,放到后边再说,这里只知道拦截器有什么就好了。

函数的返回值是用来配合eject方法使用的。

  • InterceptorManager.prototype.eject

eject方法接收一个下标,将handlers中对应下标的拦截器置为null注意这里是置为null,而不是从数组中移除这个拦截器

1
2
3
4
5
InterceptorManager.prototype.eject = function eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null;
}
};

我们在调用use方法后会得到拦截器的下标,可以使用这个下标通过eject方法来移除这个拦截器。所以在eject方法中是置为null,而不是移除元素,这样可以保证下标的准确性,不会因为调用eject方法导致拦截器的下标发生变化。

  • InterceptorManager.prototype.forEach

这个函数在Axios类中用到了,平常开发过程中我们一般是用不到这个函数的。

这个函数接收一个参数,这个参数也是一个函数,我们称之为fn。然后forEach函数内部会循环handlers中的所有拦截器,把每个拦截器作为参数传入fn执行。(这里会判断拦截器是否为null,因为我们移除拦截器会置为null

1
2
3
4
5
6
7
InterceptorManager.prototype.forEach = function forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
fn(h);
}
});
};

InterceptorManager源码

贴一下拦截器源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
'use strict';

var utils = require('./../utils');

function InterceptorManager() {
this.handlers = [];
}

InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
});
return this.handlers.length - 1;
};

InterceptorManager.prototype.eject = function eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null;
}
};

InterceptorManager.prototype.forEach = function forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
fn(h);
}
});
};

module.exports = InterceptorManager;

Axios类

看完了InterceptorManager,我们终于可以来看一下Axios类了。还是分为构造函数会原型对象方法来看。

构造函数

Axios类的构造函数也是很简单的,接收一个配置项,然后生成一些默认属性。

1
2
3
4
5
6
7
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}

对于instanceConfig,不知道大家还能否记得第一章节也就是axios.js的内容,如果忘记了需要回顾一下哦。

axios.js中我们通过new Axios创建实例,对于axios实例,这个instanceConfig就是defaults.js中的默认配置,而对于axios.create(config)创建的实例,这个instanceConfigdefaults.jsconfig合并后的配置内容。在构造函数中,我们把用于创建实例的配置项存储到this.defaults上。

Axios还维护有一个属性interceptors用来收集拦截器,我们知道拦截器构造函数的内部是维护了一个数组,所以这里Axios类中的interceptors对象的两个属性requestresponse,分别维护请求拦截器数组和响应拦截器数组。记住这里是维护了两个数组,后边还会说到他。

request方法

Axios.prototype.requestAxios类的一个核心方法了,在lib/axios.js中的createInstance方法中有这么一行代码var instance = bind(Axios.prototype.request, context);,关于这一行代码的内容我们说了挺多的,它的存在就是为了让我们方便的调用这个request方法

我们在调用request方法的时候可以传入一个参数,比如axios({method:'post',url:'/url'})这种。但是除了这种方法外request还有一种调用方式入axios.request('/url', config)

先来看一下reuqest方法中对于config的处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
Axios.prototype.request = function request(config) {
// 如果config是字符串,就是我们说的axios.request('/url', config)这种用法,那么这个字符串请求的url,后边传入的config才是额外配置,当然config不是必传的
// 所以在这里,如果config是字符串我们需要对传入的config进行合并
if (typeof config === 'string') {
config = utils.merge({
url: arguments[0]
}, arguments[1]);
}
// 这里很有意思,是对下边的配置项进行合并。我们前边说过merge方法中传入的参数如果有同名属性,后边的会覆盖掉前边的,所以可以得知同名属性优先级最高的是参数config,然后是this.defaults,再之后是方法{method:'get'},优先级最低的是默认配置,我们前边说过默认配置的内容。
config = utils.merge(defaults, { method: 'get' }, this.defaults, config);
// 请求方法转为小写
config.method = config.method.toLowerCase();
}

对于config的处理我们需要注意两点,一是config为字符串的情况。而是对所有配置项对合并,注意配置项对优先级。参数config -> this.defaults -> {method:’get’} -> defaults

再看一下request方法中剩下的内容,我们一点点来说

1
2
3
4
5
6
7
8
9
10
11
12
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;

先说一下chain这个数组,这里维护的都是成对的,比如请求拦截器的成功回调和失败回调、响应拦截器的成功回调和失败回调。而dispatchRequest这个发送请求的方法比较特殊,所以加入了一个undefined与其组成一对。看到后边的调用方式就会理解这里的。

1
var chain = [dispatchRequest, undefined];

定义了一个变量promise,赋值为Promise.resolve(config)。为什么传入config?,想一下我们的请求拦截器接收参数config并要求返回config是不是突然知道为什么传入config了,这里Promise.resolve(config)可以让我们的请求拦截器接收到config,而请求拦截器返回到config可以给后边的请求拦截器和dispatchRequest使用。

1
var promise = Promise.resolve(config);

接下来看一下请求拦截器的部分,我们收集请求拦截器肯定是每调用一次Interceptor.use就会往this.interceptors.requestpush一个对象,其中有fulfilled和rejected方法。然后在这里我们循环存储请求拦截器的数组并依次将其fulfilledrejected方法添加到chain的前边,所以最后一个定义的请求拦截器应该是chain的第一对元素哦。

1
2
3
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});

响应拦截器差不多,只是往后追加了,所以最后一个定义的响应拦截器是chain的最后一对元素

1
2
3
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
chain.push(interceptor.fulfilled, interceptor.rejected);
});

然后就是依次调用chain中的内容并将最终结果返回啦,返回结果也是一个Promise

在调用过程中传入promise.then的依次是

  • 每一组请求拦截器的成功和失败回调
  • dispathRequestundefined
  • 每一组响应拦截器的成功和失败回调
1
2
3
4
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;

在这里我们需要注意的是,请求拦截器是先定义的后执行,而响应拦截器是先定义的先执行。

request方法完整源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Axios.prototype.request = function request(config) {
if (typeof config === 'string') {
config = utils.merge({
url: arguments[0]
}, arguments[1]);
}
config = utils.merge(defaults, { method: 'get' }, this.defaults, config);
config.method = config.method.toLowerCase();
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;
};

其他扩展方法

我们都知道axios还给我们提供了指定请求方法的请求方式,比如axios.get()或者axios.post()等,其实这些方法都是基于Axios.prototype.request来实现的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
/*eslint func-names:0*/
Axios.prototype[method] = function (url, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url
}));
};
});

utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
/*eslint func-names:0*/
Axios.prototype[method] = function (url, data, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url,
data: data
}));
};
});

思路一致,如果调用get等方法等时候传入了config就将config传入request方法,但是请求方法、url这些不从config中取,因为在调用的时候已经制定了请求方法且传入了请求url。

几个方法的实现是一样的,只是post/put/patch请求可以传递data

贴一下Axios类的源码内容吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
'use strict';

var defaults = require('./../defaults');
var utils = require('./../utils');
var InterceptorManager = require('./InterceptorManager');
var dispatchRequest = require('./dispatchRequest');

function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}

Axios.prototype.request = function request(config) {
if (typeof config === 'string') {
config = utils.merge({
url: arguments[0]
}, arguments[1]);
}

config = utils.merge(defaults, { method: 'get' }, this.defaults, config);
config.method = config.method.toLowerCase();
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;
};


utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {

Axios.prototype[method] = function (url, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url
}));
};
});

utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {

Axios.prototype[method] = function (url, data, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url,
data: data
}));
};
});

module.exports = Axios;

到现在,我们已经知道整个axios的执行流程,怎么处理配置项,请求拦截和响应拦截,返回最终的promise等这一个流程已经串下来了,现在就差了关键一步,dispatchRequest中关于发送请求的详细内容是怎么实现的,毕竟axios就是用来发送请求的,这里才是真正的核心内容。

作者

胡兆磊

发布于

2022-10-12

更新于

2022-10-23

许可协议