axios 源码解析
lib/core
Axios.js
- axios 实例,接收自定义或者默认的config,并且设置了拦截器属性,分别定义了request和response的拦截器
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
- 设置原型方法,使每个实例都是得到
Axios.prototype.request = function request(config) {
/*eslint no-param-reassign:0*/
// Allow for axios('example/url'[, config]) a la fetch API
// 第一个值为string,说明应该是url
if (typeof config === 'string') {
config = arguments[1] || {};
config.url = arguments[0];
} else {
config = config || {};
}
// 合并自定义config和默认值
config = mergeConfig(this.defaults, config);
// Set config.method
if (config.method) {
config.method = config.method.toLowerCase();
} else if (this.defaults.method) {
config.method = this.defaults.method.toLowerCase();
} else {
config.method = 'get';
}
// Hook up interceptors middleware
// 这里维护了一个调用栈,中间为dispatchRequest,和undefined
var chain = [dispatchRequest, undefined];
var promise = Promise.resolve(config);
// 遍历request拦截器,将每个拦截器的resolve,reject添加在调用栈chain的前面
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
// 遍历response拦截器,将每个拦截器的resolve,reject添加在调用栈chain的前面
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
chain.push(interceptor.fulfilled, interceptor.rejected);
});
// 遍历调用栈,成双成的给promise增加then,每个then添加resolve和reject方法
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
// 最终返回的promise上,前面是request拦截器,中间是请求,后面是response拦截器
return promise;
};
- 合并自定义config和默认config,并且返回拼接好params和query的url
Axios.prototype.getUri = function getUri(config) {
config = mergeConfig(this.defaults, config);
return buildURL(config.url, config.params, config.paramsSerializer).replace(/^\?/, '');
};
- 分别给Axios添加原型方法,并处理data
// Provide aliases for supported request methods
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
/*eslint func-names:0*/
Axios.prototype[method] = function(url, config) {
return this.request(mergeConfig(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(mergeConfig(config || {}, {
method: method,
url: url,
data: data
}));
};
});
buildFullPath.js
module.exports = function buildFullPath(baseURL, requestedURL) {
if (baseURL && !isAbsoluteURL(requestedURL)) {
return combineURLs(baseURL, requestedURL);
}
return requestedURL;
};
如果存在baseUrl并且是相对路径,返回拼接好的url,否则返回原始url
createError.js
封装了error,使返回的error统一化
module.exports = function createError(message, config, code, request, response) {
var error = new Error(message);
return enhanceError(error, config, code, request, response);
};
dispatchRequest.js
/**
* Throws a `Cancel` if cancellation has been requested.
*/
function throwIfCancellationRequested(config) {
if (config.cancelToken) {
config.cancelToken.throwIfRequested();
}
}
/**
* 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) {
throwIfCancellationRequested(config);
// Ensure headers exist
config.headers = config.headers || {};
// Transform request data
config.data = transformData(
config.data,
config.headers,
config.transformRequest
);
// Flatten headers
config.headers = utils.merge(
config.headers.common || {},
config.headers[config.method] || {},
config.headers
);
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(
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);
});
};
- 当用户定义cancelToken时,会把cancel方法抛给用户,当用户触发cancel时,会通过new cancel生成reason,如果存在reason,直接throw。
- transformRequest,这块处理data和header,需要用户自定义方法,在请求发送之前处理data和header
- 处理header,将三部分header合并,第一,可以设置全局默认的header,也就是header.common,第二,每个method都设置了对应的content-type等参数,此时找到设置的config,method对应的header,第三用户设置的header
- 在上一步找到method对应的header,因此删除不必要的header.method
- 返回一个promise,调用adapter,成功和失败,都会组装resposeData,同样支持传入transformResponse来修改返回的data,成功直接返回response,失败reject
InterceptorManager
function InterceptorManager() {
this.handlers = [];
}
// 首先将拦截器的成功和失败,push到handlers,此时会返回当前拦截器的id,方便后面删除
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
});
return this.handlers.length - 1;
};
// 删除对应的拦截器,这块没有直接删除拦截器,只是把对应的拦截器id设置为空,是为了防止拦截器的id发生变化
InterceptorManager.prototype.eject = function eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null;
}
};
// 遍历拦截器数组,执行传入的方法,这里对应上面的chain执行栈,按顺序插入chain,最终会将request和response各种拦截器按顺序执行
InterceptorManager.prototype.forEach = function forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
fn(h);
}
});
};
module.exports = InterceptorManager;
mergeConfig
module.exports = function mergeConfig(config1, config2) {
// eslint-disable-next-line no-param-reassign
config2 = config2 || {};
var config = {};
var valueFromConfig2Keys = ['url', 'method', 'data'];
var mergeDeepPropertiesKeys = ['headers', 'auth', 'proxy', 'params'];
var defaultToConfig2Keys = [
'baseURL', 'transformRequest', 'transformResponse', 'paramsSerializer',
'timeout', 'timeoutMessage', 'withCredentials', 'adapter', 'responseType', 'xsrfCookieName',
'xsrfHeaderName', 'onUploadProgress', 'onDownloadProgress', 'decompress',
'maxContentLength', 'maxBodyLength', 'maxRedirects', 'transport', 'httpAgent',
'httpsAgent', 'cancelToken', 'socketPath', 'responseEncoding'
];
var directMergeKeys = ['validateStatus'];
function getMergedValue(target, source) {
// 判断是否为纯粹的对象,首先会验证[object Object],其次验证prototype是否为Object本身的prototype
// source为config2,如果两个都为存粹对象,那么合并
// utils 通过递归实现了深拷贝
// 深浅拷贝区别:浅拷贝只复制指向某个对象的指针而不复制对象本身,新旧对象还是共享同一块内存
// 深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象
if (utils.isPlainObject(target) && utils.isPlainObject(source)) {
return utils.merge(target, source);
} else if (utils.isPlainObject(source)) {
return utils.merge({}, source);
} else if (utils.isArray(source)) {
// 浅复制了原数组中的元素的一个新数组
return source.slice();
}
return source;
}
// 此处getMergedValue和上面的区别是,上面不关注config1的属性,只要config2有就复制,mergeDeepProperties则是若不存在config2,会复制config1
function mergeDeepProperties(prop) {
if (!utils.isUndefined(config2[prop])) {
config[prop] = getMergedValue(config1[prop], config2[prop]);
} else if (!utils.isUndefined(config1[prop])) {
config[prop] = getMergedValue(undefined, config1[prop]);
}
}
utils.forEach(valueFromConfig2Keys, function valueFromConfig2(prop) {
if (!utils.isUndefined(config2[prop])) {
config[prop] = getMergedValue(undefined, config2[prop]);
}
});
utils.forEach(mergeDeepPropertiesKeys, mergeDeepProperties);
utils.forEach(defaultToConfig2Keys, function defaultToConfig2(prop) {
if (!utils.isUndefined(config2[prop])) {
config[prop] = getMergedValue(undefined, config2[prop]);
} else if (!utils.isUndefined(config1[prop])) {
config[prop] = getMergedValue(undefined, config1[prop]);
}
});
// 这里使用in,只要对象上包含该key,就返回true(自身属性和prototype上)
// hasOwnProperty 对象自身属性上包含则返回true
// 判断对象那些属性为原型中的属性,!Object.hasOwnProperty && in
utils.forEach(directMergeKeys, function merge(prop) {
if (prop in config2) {
config[prop] = getMergedValue(config1[prop], config2[prop]);
} else if (prop in config1) {
config[prop] = getMergedValue(undefined, config1[prop]);
}
});
var axiosKeys = valueFromConfig2Keys
.concat(mergeDeepPropertiesKeys)
.concat(defaultToConfig2Keys)
.concat(directMergeKeys);
var otherKeys = Object
.keys(config1)
.concat(Object.keys(config2))
.filter(function filterAxiosKeys(key) {
return axiosKeys.indexOf(key) === -1;
});
utils.forEach(otherKeys, mergeDeepProperties);
return config;
};
- 合并用户自定义的参数:url,method,data,如果config2中不是undefined,则使用config2的内容,这些多数为必传字段,因此以config2为主
- 合并mergeDeepPropertiesKeys,这些参数都是可选,因此如果用户没有手动设置,取默认值,也就是为什么要判断是否存在config1[props],如果存在config2,同时会合并config1和config2
- 合并defaultToConfig2Keys,这些参数是可选的,是axios已有默认值的配置项,和上面参数的区别在于这一类参数用于控制请求过程,包括变量和函数,这些是可选的,如果用户设置,以用户设置为主,否则采用默认的参数
- 处理剩余的字段,剔除axiosKey中包含的,复制剩余的字段
transformData
module.exports = function transformData(data, headers, fns) {
/*eslint no-param-reassign:0*/
utils.forEach(fns, function transform(fn) {
data = fn(data, headers);
});
return data;
};
fns可以是一个函数也可以是一个函数数组,data和headers将执行每个传入的fn