axios 对象和方法
# Axios 对象
Axios 对象通过 ES5 语法构建。
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
1
2
3
4
5
6
7
2
3
4
5
6
7
从 OOP 的角度看,和 ES6 语法相比,类的构造器、实例方法、静态方法写法不同,可以作如下对比:
ES6 | ES5 | |
---|---|---|
构造器 | constructor 函数 | 函数体 |
调用福构造器 | super() | SuperClass.call(this) |
静态方法、静态属性 | static property | Class.property |
实例方法、实例属性 | this.property | Class.prototype.property |
实例化 | new Class() | new Class() |
this | 指向实例 | 指向实例 |
继承 | extends 关键字 | 原型继承 |
ES5 中类的继承:
/** Inherit the prototype methods from one constructor into another */
function inherits(constructor, superConstructor, props, descriptors) {
constructor.prototype = Object.create(superConstructor.prototype, descriptors);
constructor.prototype.constructor = constructor;
props && Object.assign(constructor.prototype, props);
}
1
2
3
4
5
6
2
3
4
5
6
如何使用 ES5 语法继承父类?
- 根据父类的原型创建一个原型,并作为子类的原型。
- 将子类的构造器添加到子类的原型上。
- 在子类的原型上添加子类额外的实例方法和实例属性。
# 任务链
在 axios 中,将 request 拦截器、dispatchRequest (发送请求)、response 拦截器按照 FIFO(先进先出,队列顺序)构成任务链,并且根据任务的同步或者异步,区分为同步任务链和异步任务链来进行处理。这种处理方法,对于程序的可扩展性、灵活性有很大的益处,可谓是眼睛一亮。
事实上,在很多其他类型需要对每个行为进行横向扩展,如拦截、代理、任务链、过滤的情境中,都可以参考这种思想。同时这种链式处理思想在责任链设计模式、代理模式中都或多或少的有所体现,在 single-spa 等源码中也能看到类似的使用方法。另外在 transformData 中也体现了这种思想:
/**
* Transform the data for a request or a response
*
* @param {Object|String} data The data to be transformed
* @param {Array} headers The headers for the request or response
* @param {Number} status HTTP status code
* @param {Array|Function} fns A single function or Array of functions
*
* @returns {*} The resulting transformed data
*/
module.exports = function transformData(data, headers, status, fns) {
var context = this || defaults;
/*eslint no-param-reassign:0*/
utils.forEach(fns, function transform(fn) {
data = fn.call(context, data, headers, status);
});
return data;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
下面我们来着重看下 Axios.prototype.request
方法的代码:
/**
* Dispatch a request
*
* @param {String|Object} configOrUrl The config specific for this request (merged with this.defaults)
* @param {?Object} config
*
* @returns {Promise} The Promise to be fulfilled
*/
Axios.prototype.request = function request(configOrUrl, config) {
// ......
config = mergeConfig(this.defaults, config);
// ......
// filter out skipped interceptors
var requestInterceptorChain = [];
var synchronousRequestInterceptors = true;
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {
return;
}
synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;
requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
});
var responseInterceptorChain = [];
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
});
var promise;
if (!synchronousRequestInterceptors) {
var chain = [dispatchRequest, undefined];
Array.prototype.unshift.apply(chain, requestInterceptorChain);
chain = chain.concat(responseInterceptorChain);
promise = Promise.resolve(config);
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
}
var newConfig = config;
while (requestInterceptorChain.length) {
var onFulfilled = requestInterceptorChain.shift();
var onRejected = requestInterceptorChain.shift();
try {
newConfig = onFulfilled(newConfig);
} catch (error) {
onRejected(error);
break;
}
}
try {
promise = dispatchRequest(newConfig);
} catch (error) {
return Promise.reject(error);
}
while (responseInterceptorChain.length) {
promise = promise.then(responseInterceptorChain.shift(), responseInterceptorChain.shift());
}
return promise;
};
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
67
68
69
70
71
72
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
67
68
69
70
71
72
这里有一下几点技巧值得我们注意:
- 任务 request 都需要经过
interceptors.request
、dispatchRequest
和interceptors.response
的链式处理,根据interceptors.request
是否有异步的拦截器,可以将其同步执行或者异步执行。 - 以异步执行为例,整个 chain 中的结构为
requestInterceptorChain + [dispatchRequest, undefined] + responseInterceptorChain
,每个 interceptorChain 中包含onFulfilled
和onRejected
,分别用于当前拦截器处理成功的回调和失败的回调。那个为什么 dispatchRequest 后面要加一个undefined
呢?这是因为 chain 中每次出队列都是两个,因此整个队列在 promise 的驱动下才得以运行。 - Array 的 push、concat、splice、unshift 方法支持传入多个 item 的用法。可以看下如下的类型:
interface Array<T> {
push(...items: T[]): number;
concat(...items: ConcatArray<T>[]): T[];
concat(...items: (T | ConcatArray<T>)[]): T[];
splice(start: number, deleteCount: number, ...items: T[]): T[];
unshift(...items: T[]): number;
// ......
1
2
3
4
5
6
7
2
3
4
5
6
7
# dispatchRequest
/**
* Dispatch a request to the server using the configured adapter.
*
* @param {object} config The config that is to be used for the request
*
* @returns {Promise} The Promise to be fulfilled
*/
module.exports = function dispatchRequest(config) {
// ......
// Transform request data
// 利用 transformRequest 对 data 经过处理
config.data = transformData.call(
config,
config.data,
config.headers,
null,
config.transformRequest
);
// ......
utils.forEach(
["delete", "get", "head", "post", "put", "patch", "common"],
function cleanHeaderConfig(method) {
delete config.headers[method];
}
);
var adapter = config.adapter || defaults.adapter;
return adapter(config).then(
function onAdapterResolution(response) {
throwIfCancellationRequested(config);
// Transform response data
response.data = transformData.call(
config,
response.data,
response.headers,
response.status,
config.transformResponse
);
return response;
},
function onAdapterRejection(reason) {
if (!isCancel(reason)) {
throwIfCancellationRequested(config);
// Transform response data
if (reason && reason.response) {
reason.response.data = transformData.call(
config,
reason.response.data,
reason.response.headers,
reason.response.status,
config.transformResponse
);
}
}
return Promise.reject(reason);
}
);
};
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
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
这里有几点需要注意:
- 2 个 transformer: transformRequest 和 transformResponse 的 transformer 管道。
- adapter:发送请求的适配器。因为 axios 要适配各种 runtime,如 web 环境和 Node 环境,并且掩盖各种浏览器的关于发送请求得差异,因此采用适配器来处理请求发送。除此之外,这么做还有另一个好处,那就是提供用于自定义 adapter 的能力,帮助开发者是够适配各种非内置的运行环境中的请求发送的需求。
编辑 (opens new window)
上次更新: 2022/09/06, 14:25:16