Axios用法与原理
# # Axios用法与原理
axios是一个非常小巧而好用的http请求库,支持promise以及同时支持浏览器和node端。axios使用简单,配置灵活,也是vue官方推荐的请求库。另外axios源码 (opens new window) (opens new window)层次清晰明了,非常适合阅读。
# # 特性
- 从浏览器中创建 XMLHttpRequest
- 从 node.js 发出 http 请求
- 支持 Promise API
- 拦截请求和响应
- 转换请求和响应数据
- 取消请求
- 自动转换JSON数据
- 客户端支持防止 CSRF/XSRF
# # API
- 全局
axios.request(config)
最终http请求都是执行这个方法axios(config)
和axios.request()等价axios(url[, config])
axios(config)快捷方式axios.[METHODS](url, config)
axios(config)快捷方式
- 自定义实例
axios.create(config)
自定义配置,创建实例instance。调用方式和axios方法一致
- 拦截器
axios.interceptors.request.use
axios.interceptors.response.use
// 以下实例等价
// 全局调用
axios({
method:'get',
url:'http://bit.ly/2mTM3nY',
field: 123
}) // axios(config)
axios('http://bit.ly/2mTM3nY', {field: 123}) // axios(url[, config])
axios.get('http://bit.ly/2mTM3nY', {field: 123}) // axios.[METHODS](url, config)
// 自定义实例调用
const instance = axios.create({
baseURL: 'http://bit.ly'
});
instance({
method:'get',
url:'2mTM3nY',
field: 123
}) // instance(config)
instance.get('2mTM3nY', {field: 123}) // instance.[METHODS](url, config)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
配置优先级:lib / defaults.js中的库默认值 -->实例的config属性--> 请求的config参数
# # 为何axios有如此多使用方式
重点是createInstance
方法,该方法拿到一个Function,该Function指向请求入口Axios.prototype.request,并且该Function还继承了Axios.prototype的每个方法,并且上下文指向同一个对象context。axios包默认导出是该Function,而自定义实例axios.create是一个工厂模式,最终都调用createInstance方法。
源码在lib/default.js中:
function createInstance(defaultConfig) {
var context = new Axios(defaultConfig);
// instance指向了request方法,且上下文指向context
// instance(config) = Axios.prototype.request(config)
var instance = bind(Axios.prototype.request, context);
// 把Axios.prototype上的方法扩展到instance对象上
// 这样 instance 就有了 get、post、put等METHOD方法
// 同时指定上下文为context,这样执行Axios原型链上的方法时,this会指向context
utils.extend(instance, Axios.prototype, context);
// 把context对象上的自身属性和方法扩展到instance上
utils.extend(instance, context);
return instance;
}
// 导出时就创建一个默认实例,所以可以通过axios(config)发出请求
var axios = createInstance(defaults);
axios.Axios = Axios;
// 工厂模式创建axios实例,其实最终都是调用createInstance方法。
// 所以实例调用方式和全局axios调用方式相同。instance(config) = axios(config)
axios.create = function create(instanceConfig) {
return createInstance(mergeConfig(axios.defaults, instanceConfig));
};
module.exports = axios;
module.exports.default = axios; // 允许在ts中导入
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
# # 整体流程
请求流程实质上无非就是输入和输出,输入request config配置(如url、method、data等),输出最终的response data数据。中间涉及到较多的数据预处理,如根据data数据类型(如JSON),设置Content-Type: json头信息;再如根据响应的数据类型,默认转换为json格式。这些预处理需要axios库自动完成,同时也要暴露出相关配置给开发者,以此实现开发者自定义预处理。
Axios通过拦截者(Interceptors)来实现上面说的数据预处理,具体通过使用chain链式来逐一插入处理逻辑。默认情况下,先拿到此次请求的config配置,把config作为参数,传递到dispatchRequest
方法中,该方法会根据当前环境(Browser/Node)来调用不同的adapter发送请求,以此获得最终的response data。同时Axios还允许使用request/response Interceptors进行请求前和响应后的自定义处理,其实质是将预处理函数,插入chain数组中,再统一按照顺序逐步执行预处理方法(Promise保证顺序)。
Axios类是核心内容,该类request方法是所有请求的开始入口。源码在lib/core/Axios.js:
Axios.prototype.request = function request(config) {
// 1. 允许 axios('url'[, config]) = axios(config)
if (typeof config === 'string') {
config = arguments[1] || {};
config.url = arguments[0];
} else {
config = config || {};
}
// 配置文件合并策略优先级
config = mergeConfig(this.defaults, config);
config.method = config.method ? config.method.toLowerCase() : 'get';
// 2. 定义拦截器中间件钩子
// dispatchRequest是真正开始下发请求,执行config中设置的adapter方法
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);
});
// 3. chain链关键代码,promise链路传递下去
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
};
// 提供对request方法的METHOD快捷方式,实质都是调用Axios.prototype.request方法
// axios.get(url, config) = axios(config)
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
}));
};
});
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
# # 智能的默认设置和扩展
- 根据环境自动设置请求的adapter,同时支持自定义
- 自动根据请求data数据类型,设置headers。比如Content-Type自动设置
- 自动根据响应data数据类型,转为json
- 支持自定义转换请求和响应数据
源码在lib/default.js中
var defaults = {
// 根据环境选择默认的请求方式。支持node和浏览器,也可以自定义adapter
adapter: getDefaultAdapter(),
// 请求转换
transformRequest: [function transformRequest(data, headers) {
normalizeHeaderName(headers, 'Accept');
normalizeHeaderName(headers, 'Content-Type');
if (utils.isFormData(data) ||
utils.isArrayBuffer(data) ||
utils.isBuffer(data) ||
utils.isStream(data) ||
utils.isFile(data) ||
utils.isBlob(data)
) {
return data;
}
if (utils.isArrayBufferView(data)) {
return data.buffer;
}
if (utils.isURLSearchParams(data)) {
setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');
return data.toString();
}
if (utils.isObject(data)) { // 对象数据时,自动设置Content-Type为json格式。
setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
return JSON.stringify(data);
}
return data;
}],
// 响应转换
transformResponse: [function transformResponse(data) {
/*eslint no-param-reassign:0*/
if (typeof data === 'string') {
try {
data = JSON.parse(data);
} catch (e) { /* Ignore */ }
}
return data;
}],
timeout: 0,
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
validateStatus: function validateStatus(status) {
return status >= 200 && status < 300;
}
};
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
# # 总结
请求从用户调用看,只是设置好config配置,剩下的具体底层调用应该交给专业库去做。axios库就是其中佼佼者,它较好的封装了http请求流程,支持多种形式config merge,通过抽象adapter层(通过是否有XMLHttpRequest对象来判断),来兼容浏览器以及Node环境。通过chain链式方式,不仅预处理请求/响应数据,同时支持用户自定义拦截处理(Interceptors),以此实现用户简约调用。