经过上文的学习,已经对axios源码有了一些了解,知道了axios对象中包含哪些属性和方法,以及最终导出的axios其实是一个函数。但是对其中实例的原理,以及各种方法的原理却没有深入,从这一篇文章就开始对这些内容的学习。
Axios.js
在这个文件中,主要内容可分为五个部分:
- 小模块的导入
- Axios函数对象的声明
- 原型方法request和getUri
- 给请求方法设置别名
- 导出Axios
小模块的导入
var utils = require('./../utils');
var buildURL = require('../helpers/buildURL');
var InterceptorManager = require('./InterceptorManager');
var dispatchRequest = require('./dispatchRequest');
var mergeConfig = require('./mergeConfig');
var validator = require('../helpers/validator');
Axios函数对象
在上一文章中,我们有提到够Axios实例的内容:defaults属性和interceptors属性,这两个属性的具体内容如下:
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
- defaults = instanceConfig 为Axios对象的默认配置内容
- interceptors 为拦截器对象,里面包含两个拦截器:请求拦截器和响应拦截器
原型方法getUri
整体代码
Axios.prototype.getUri = function getUri(config) {
config = mergeConfig(this.defaults, config);
return buildURL(config.url, config.params, config.paramsSerializer).replace(/^?/, '');
};
mergeConfig方法解析
module.exports = function mergeConfig(config1, config2) {
config2 = config2 || {};
var config = {};
var mergeMap = {
'url': valueFromConfig2,
'method': valueFromConfig2,
'data': valueFromConfig2,
'baseURL': defaultToConfig2,
'transformRequest': defaultToConfig2,
'transformResponse': defaultToConfig2,
'paramsSerializer': defaultToConfig2,
'timeout': defaultToConfig2,
'timeoutMessage': defaultToConfig2,
'withCredentials': defaultToConfig2,
'adapter': defaultToConfig2,
'responseType': defaultToConfig2,
'xsrfCookieName': defaultToConfig2,
'xsrfHeaderName': defaultToConfig2,
'onUploadProgress': defaultToConfig2,
'onDownloadProgress': defaultToConfig2,
'decompress': defaultToConfig2,
'maxContentLength': defaultToConfig2,
'maxBodyLength': defaultToConfig2,
'transport': defaultToConfig2,
'httpAgent': defaultToConfig2,
'httpsAgent': defaultToConfig2,
'cancelToken': defaultToConfig2,
'socketPath': defaultToConfig2,
'responseEncoding': defaultToConfig2,
'validateStatus': mergeDirectKeys
};
utils.forEach(Object.keys(config1).concat(Object.keys(config2)), function computeConfigValue(prop) {
var merge = mergeMap[prop] || mergeDeepProperties;
var configValue = merge(prop);
(utils.isUndefined(configValue) && merge !== mergeDirectKeys) || (config[prop] = configValue);
});
return config;
}
bindURL方法解析
module.exports = function buildURL(url, params, paramsSerializer) {
if (!params) {
return url;
}
var serializedParams;
if (paramsSerializer) {
serializedParams = paramsSerializer(params);
} else if (utils.isURLSearchParams(params)) {
serializedParams = params.toString();
} else {
var parts = [];
utils.forEach(params, function serialize(val, key) {
if (val === null || typeof val === 'undefined') {
return;
}
if (utils.isArray(val)) {
key = key + '[]';
} else {
val = [val];
}
utils.forEach(val, function parseValue(v) {
if (utils.isDate(v)) {
v = v.toISOString();
} else if (utils.isObject(v)) {
v = JSON.stringify(v);
}
parts.push(encode(key) + '=' + encode(v));
});
});
serializedParams = parts.join('&');
}
if (serializedParams) {
var hashmarkIndex = url.indexOf('#');
if (hashmarkIndex !== -1) {
url = url.slice(0, hashmarkIndex);
}
url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams;
}
return url;
};
原型方法request
方法的代码太长,分作几部分介绍:
- 配置项处理
- 请求方式处理
- 对transitional属性进行版本校验
- 拦截器处理
配置项处理
if (typeof config === 'string') {
config = arguments[1] || {};
config.url = arguments[0];
} else {
config = config || {};
}
config = mergeConfig(this.defaults, config);
这样处理的原因在于传入配置参数的不同。
还记得在第一篇文章中介绍axios使用方式时的API方式吗?里面分为了两种使用方式。
axios({
method: 'get',
url: 'xxx',
data: {}
});
- axios(url,config) config可不传
axios('http://xxx');
axios('http://xxx',{
xxx
});
这里的第一个判断条件就是axios(url,config)的情况,第二个是axios(config)的情况,最终将传入的配置和默认配置项进行合并。
请求方式处理
if (config.method) {
config.method = config.method.toLowerCase();
} else if (this.defaults.method) {
config.method = this.defaults.method.toLowerCase();
} else {
config.method = 'get';
}
由于前端传入时,请求方式大小写形式都有,所以要统一处理成小写形式,以及设置了默认的请求方式
对transitional属性进行版本校验
var transitional = config.transitional;
if (transitional !== undefined) {
validator.assertOptions(transitional, {
silentJSONParsing: validators.transitional(validators.boolean),
forcedJSONParsing: validators.transitional(validators.boolean),
clarifyTimeoutError: validators.transitional(validators.boolean)
}, false);
}
这段代码是对transitional 属性进行版本校验,会提示自某个版本之后移除该属性。
我在网上看到过validators.boolean会跟一个’1.0.0’版本号,但是我从gitee网站上clone的最新代码是没有,不晓得是哪里的改变了。
拦截器处理
这一部分,我也是看不太懂。在网上找到了仙凌阁大大的文章https://blog.csdn.net/qq_39221436/article/details/120652086
复制了拦截器部分的解读代码,由于这部分代码经过一次重构,所以两种代码的展示都放到下面了。
重构前的代码内容不多,看起来也不是很难以理解,更便于初学者学习。
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;
promise链是从左到右执行的,先执行请求拦截器,再执行请求,最后执行响应拦截器。
由于拦截器可以有多个(所以用forEach),所以会有顺序问题,由于unshift和push的方法
- 请求拦截器中后加的拦截器会被放到前面,先执行
- 响应拦截器中后加的拦截器会被放到后面,后执行
- 重构后
主要是为了解决请求拦截器中可能会出现异步情况或当前宏任务执行时间过长而阻塞真正请求执行的问题
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;
给请求方法设置别名
设置别名的方式更便于axios的使用,在第一篇文章中,我们也列举了别名方式的使用示例。
而在设置别名的时候,由于有些方式是没有data参数的,所以也需要分类处理。
- 无Data参数:delete、get、head、options
- 有Data参数:post、put、patch
utils.forEach(['delete', 'get', 'head', 'options'],function forEachMethodNoData(method) {
Axios.prototype[method] = function(url, config) {
return this.request(mergeConfig(config || {}, {
method: method,
url: url,
data: (config || {}).data
}));
};
});
utils.forEach(['post', 'put', 'patch'],function forEachMethodWithData(method) {
Axios.prototype[method] = function(url, data, config) {
return this.request(mergeConfig(config || {}, {
method: method,
url: url,
data: data
}));
};
});
导出axios
module.exports = Axios;
|