为了方便使用,axios对象既能做对象使用,又能做函数使用.
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
axios({
method: 'post',
url: '/user/12345',
data: {
firstName: 'Fred',
lastName: 'Flintstone'
}
});
这一点axios是如何做到的,可以看到instance其实是一个绑定this的函数,调用axios就是调用context.request
function createInstance(){
// 能当做函数使用的秘密
var instance = bind(Axios.prototype.request, context);
// 能当做对象使用的秘密
utils.extend(instance, Axios.prototype, context);
// 要拿到构造函数继承的属性
utils.extend(instance, context);
return instance
}
var axios = createInstance(defaults);
接下来我们看一下request方法,所有http请求的发送都会调用Axios.prototype.request
,这个函数可以认为是整个axios的骨架,非常重要。
Axios.prototype.request = function request(config) {
// 每个请求都会重新合成一个config,所以通过操作config对象,你可以标识请求,做某些操作,事实上每个axios的拦截器都能拿到config对象
config = utils.merge(defaults, this.defaults, { method: 'get' }, 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;
}
从拦截器中的主要逻辑,我们可以得到以下几点:
- 发送请求的整个执行顺序是,requestInterceptors ——》dispatchRequest ——》responseInterceptors
- 拦截器最初接收的对象是config,axios使用中也规定,请求的拦截器必须要返回config,这也是每个请求拦截器的函数参数是config的原因
- 拦截器的执行顺序与
interceptors.request.use(function () {/*...*/})
执行的顺序有关,即先use
的请求拦截器会先执行。 - 如果拦截器中的函数时async函数,会阻塞整个拦截器链的执行,而transformData不会,所以如果需要对请求的数据做异步处理的话,要在拦截器中完成。
看一下,不同的http method是怎么复用request方法的
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
}));
};
});
接下来我们看dispatchRequest的核心逻辑:
// 处理config...
var adapter = config.adapter || defaults.adapter;
return adapter(config).then(function onAdapterResolution(response) {
throwIfCancellationRequested(config);
// Transform response data
response.data = transformData(
response.data,
response.headers,
config.transformResponse
);
return response;
}, function onAdapterRejection(reason) {
if (!isCancel(reason)) {
throwIfCancellationRequested(config);
// Transform response data
if (reason && reason.response) {
reason.response.data = transformData(
reason.response.data,
reason.response.headers,
config.transformResponse
);
}
}
return Promise.reject(reason);
});
可以看到dispatchRequest的核心逻辑大概有三步
- 处理config
- 使用adapter发送请求,axios默认内置两个adapter,一个是负责在brower发送请求的,一个是负责在node端发送请求,可以在根文件的defaults下看到
- 构造响应数据
所以通过dispatchRequest方法的阅读,我们可以得到以下启示:
- adapter是可以替换的,所以如果你觉得你的xhr或http的逻辑更适合业务的需要,完全可以替换掉,你也完全可以开发出第三种adapter以处理特定情况,比如开发一个处理缓存的adapter,事实上我现在的项目就是这样做的。
- 响应的拦截器接收到的是response对象
至此,我们已经把axios的核心逻辑阅读完毕,从中我们也可以看到axios的易用性和可拓展性非常强。
尤其是可拓展性,发送请求到接收响应的过程中的所有部分几乎都是可拓展的,尤其是config,adapter,interceptor留下了很多想象的空间。