上篇 Axios 源码解析(四):核心工具方法(1) 解析了 default.js 和 /cancel 目录下的源码,下面继续解析 /cancel 目录下核心工具方法的代码。
在github.com/MageeLin/ax… 中的 analysis 分支可以看到当前已解析完的文件。
总览
在《Axios 源码解析(一):模块分解》中已经分析过,/core 目录中包含如下这些文件:
├─core
│ Axios.js
│ buildFullPath.js
│ createError.js
│ dispatchRequest.js
│ enhanceError.js
│ InterceptorManager.js
│ mergeConfig.js
│ README.md
│ settle.js
│ transformData.js
同样在 README.md 中,介绍了该目录的作用:
core/中的模块是特定于axios内部使用的模块。因为它们的逻辑特定于专门情况,所以在axios模块之外使用这些模块很可能没有用。core模块的一些例子如下:
- 调度请求
- 通过
adapters/发送的请求(参见lib/adapters/README.md)- 管理拦截器
- 处理配置
整个 core/ 目录中,Axios.js 是最复杂也是最核心的,放在最后,剩余的文件咱们挨个来看:
buildFullPath.js
这个 buildFullPath 方法组合了上篇分析过的 isAbsoluteURL 和 combineURLs 方法,最终目的让返回的 url 必须为一个绝对地址。
var isAbsoluteURL = require('../helpers/isAbsoluteURL');
var combineURLs = require('../helpers/combineURLs');
/**
* 仅当请求的 URL 不是绝对 URL 时,才通过将 baseURL 与请求的 URL 组合来创建新 URL。
* 如果请求的 URL 是绝对URL,此函数会原封不动地返回所请求的 URL。
*
* @param {string} baseURL base URL
* @param {string} requestedURL 要组合的url(绝对或相对)
* @returns {string} 组合后的url
*/
module.exports = function buildFullPath(baseURL, requestedURL) {
// 仅当请求的 URL 不是绝对 URL 时,才组合
if (baseURL && !isAbsoluteURL(requestedURL)) {
return combineURLs(baseURL, requestedURL);
}
// 否则直接返回
return requestedURL;
};
enhanceError.js
enhanceError 方法就是单纯为了升级 Error 对象,给 Axios 生成的错误对象添加 config、code、request 和 response 属性
/**
* 使用指定的配置、错误代码和响应来升级Error。
*
* @param {Error} error 要升级的error对象
* @param {Object} config 配置.
* @param {string} [code] 错误代码 (比如, 'ECONNABORTED').
* @param {Object} [request] 请求对象.
* @param {Object} [response] 响应对象.
* @returns {Error} 返回升级后的error对象.
*/
module.exports = function enhanceError(error, config, code, request, response) {
// 把各种属性赋给error对象
error.config = config;
if (code) {
error.code = code;
}
error.request = request;
error.response = response;
error.isAxiosError = true;
// 自定义一个toJSON方法,进行了序列化
error.toJSON = function toJSON() {
return {
// Standard
message: this.message,
name: this.name,
// Microsoft
description: this.description,
number: this.number,
// Mozilla
fileName: this.fileName,
lineNumber: this.lineNumber,
columnNumber: this.columnNumber,
stack: this.stack,
// Axios
config: this.config,
code: this.code,
};
};
return error;
};
createError.js
enhanceError 方法增强的就是 createError 方法生成的对象,Error 对象刚生成的时候只有一个 message 属性。
var enhanceError = require('./enhanceError');
/**
* 使用指定的message、配置、错误代码、请求和响应来创建Error。
*
* @param {string} message 错误 message.
* @param {Object} config 配置.
* @param {string} [code] 错误代码 (比如 'ECONNABORTED').
* @param {Object} [request] 请求对象.
* @param {Object} [response] 响应对象.
* @returns {Error} 创建后的错误对象.
*/
module.exports = function createError(
message,
config,
code,
request,
response
) {
// 创建一个错误后给它添加各种属性
var error = new Error(message);
return enhanceError(error, config, code, request, response);
};
settle.js
settle 方法是用来 settle 请求过程这个 promise 的,结果是 resolve 还是 reject 取决于 response。
如果响应成功,则 resolve(response);如果响应失败,则用 createError 生成一个错误对象,然后 reject 掉。
var createError = require('./createError');
/**
* 依据响应状态来 resolve 或 reject 一个 Promise.
*
* @param {Function} resolve resolve函数.
* @param {Function} reject reject函数.
* @param {object} response 响应对象.
*/
module.exports = function settle(resolve, reject, response) {
var validateStatus = response.config.validateStatus;
// 只有validateStatus方法校验通过时,才会resolve
if (!response.status || !validateStatus || validateStatus(response.status)) {
resolve(response);
// 否则reject升级后的错误对象
} else {
reject(
createError(
'Request failed with status code ' + response.status,
response.config,
null,
response.request,
response
)
);
}
};
mergeConfig.js
由于 Axios 中有默认配置、用户默认配置和用户自定义配置多种 config,所以 mergeConfig 就是用专门的规则来合并这些配置的,会返回一个新的合并后配置对象。
merge 的规则比较复杂,针对不同的属性需要有不同的合并策略:
'use strict';
var utils = require('../utils');
/**
* 配置专用的的合并函数,通过将两个配置对象合并在一起,创建一个新的配置对象。
*
* @param {Object} config1
* @param {Object} config2
* @returns {Object} 从config1和config2合并出来的新对象
*/
module.exports = function mergeConfig(config1, config2) {
// 给config2默认为空对象
// eslint-disable-next-line no-param-reassign
config2 = config2 || {};
var config = {};
// 全部来源于config2的键
var valueFromConfig2Keys = ['url', 'method', 'data'];
// 优先来源于config2的键,深度合并会判别undefined
var mergeDeepPropertiesKeys = ['headers', 'auth', 'proxy', 'params'];
// 优先来源于config2的键,不进行深度合并
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',
];
// 优先来源于config2的键,直接深度合并 使用in来判断
var directMergeKeys = ['validateStatus'];
/**
* @description: 两个同名属性 merge 时的规则,source 比 target 优先级高
* @param {Object} target 要合并的属性
* @param {Object} source 源属性
* @return {Object} 最后返回source
*/
function getMergedValue(target, source) {
// 普通对象直接执行merge方法
if (utils.isPlainObject(target) && utils.isPlainObject(source)) {
return utils.merge(target, source);
// 如果target不是纯对象,就把target抛弃
} else if (utils.isPlainObject(source)) {
return utils.merge({}, source);
// 如果source是个数组,就返回source的副本
} else if (utils.isArray(source)) {
return source.slice();
}
return source;
}
/**
* @description: 深度合并方法
* @param {string} prop 键
*/
function mergeDeepProperties(prop) {
// 如果config2中该键存在,就直接执行合并
if (!utils.isUndefined(config2[prop])) {
config[prop] = getMergedValue(config1[prop], config2[prop]);
// 如果config2中该键不存在,但config1中该键存在,就直接取config1
} else if (!utils.isUndefined(config1[prop])) {
config[prop] = getMergedValue(undefined, config1[prop]);
}
}
// 1.如果是需要config2的键,就直接从config2中把值拿过来,绝对不会取config1
utils.forEach(valueFromConfig2Keys, function valueFromConfig2(prop) {
if (!utils.isUndefined(config2[prop])) {
config[prop] = getMergedValue(undefined, config2[prop]);
}
});
// 2.需要深度合并的键就执行深度合并方法
utils.forEach(mergeDeepPropertiesKeys, mergeDeepProperties);
// 3.config2优先的键,就优先取config2,如果没有才取config1
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]);
}
});
// 4.直接合并,这个跟深度合并的区别是用in来做判别,也就是undefined的区别
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]);
}
});
// axios专用的配置的键
var axiosKeys = valueFromConfig2Keys
.concat(mergeDeepPropertiesKeys)
.concat(defaultToConfig2Keys)
.concat(directMergeKeys);
// 用户加的自定义配置的键
var otherKeys = Object.keys(config1)
.concat(Object.keys(config2))
.filter(function filterAxiosKeys(key) {
// 把axios专用的键过滤掉
return axiosKeys.indexOf(key) === -1;
});
// 5.用户自定义配置的键执行深度合并
utils.forEach(otherKeys, mergeDeepProperties);
return config;
};
transformData.js
transformData 方法是用来支持 transformRequest 和 transformResponse 的,transformRequest 允许在向服务器发送请求前修改请求数据,transformResponse 允许在传递结果给 then/catch 前修改响应数据。
var utils = require('./../utils');
var defaults = require('./../defaults');
/**
* 转换request或者response对象
*
* @param {Object|String} data 要被转换的对象
* @param {Array} headers 请求或响应的header
* @param {Array|Function} fns 单个函数或者函数数组
* @returns {*} 转换后的对象
*/
module.exports = function transformData(data, headers, fns) {
var context = this || defaults;
/*eslint no-param-reassign:0*/
utils.forEach(fns, function transform(fn) {
// 主要是调用了fn方法来转换
data = fn.call(context, data, headers);
});
return data;
};
dispatchRequest.js
dispatchRequest 方法最直接的执行发送请求的方法,它分为如下几步:
- 根据配置转换请求对象
- 整理请求头
- 选择一个适配器并发送请求
- 根据配置转换响应对象
- 返回响应
var utils = require('./../utils');
var transformData = require('./transformData');
var isCancel = require('../cancel/isCancel');
var defaults = require('../defaults');
/**
* 如果取消请求的行为已经执行,则抛出一个 Cancel 对象
*/
function throwIfCancellationRequested(config) {
if (config.cancelToken) {
config.cancelToken.throwIfRequested();
}
}
/**
* 使用已经配置好的适配器,向服务器发送请求
*
* @param {object} config 被用于发送请求的配置
* @returns {Promise} 处理完的promise
*/
module.exports = function dispatchRequest(config) {
// 若请求已取消,则抛出Cancel对象
throwIfCancellationRequested(config);
// 确保header存在
config.headers = config.headers || {};
// 利用header和data,以及请求转换器来转换data
config.data = transformData.call(
config,
config.data,
config.headers,
config.transformRequest
);
// 合并请求头
config.headers = utils.merge(
config.headers.common || {},
config.headers[config.method] || {},
config.headers
);
// 清理掉headers中的请求method
utils.forEach(
['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
function cleanHeaderConfig(method) {
delete config.headers[method];
}
);
// 选择一个适配器
var adapter = config.adapter || defaults.adapter;
// 返回一个promise
return adapter(config).then(
function onAdapterResolution(response) {
// 同样,若请求已取消,则抛出Cancel对象
throwIfCancellationRequested(config);
// 利用header和data,以及响应转换器来转换data
response.data = transformData.call(
config,
response.data,
response.headers,
config.transformResponse
);
return response;
},
function onAdapterRejection(reason) {
// 同样,若请求已取消,则抛出Cancel对象
if (!isCancel(reason)) {
throwIfCancellationRequested(config);
// 利用header和data,以及响应转换器来转换data
if (reason && reason.response) {
// 添加到reason下的response属性中
reason.response.data = transformData.call(
config,
reason.response.data,
reason.response.headers,
config.transformResponse
);
}
}
return Promise.reject(reason);
}
);
};
InterceptorManager.js
InterceptorManager 是拦截器的类,使用栈保存了自定义的拦截器,use 方法安装拦截器,eject 方法卸载拦截器,forEach 迭代每一个拦截器。
var utils = require('./../utils');
function InterceptorManager() {
this.handlers = [];
}
/**
* 给栈中添加一个新的拦截器
*
* @param {Function} fulfilled fulfilled后怎么处理then的函数
* @param {Function} rejected reject后怎么处理reject的函数
*
* @return {Number} 便于之后删除拦截器用的ID
*/
InterceptorManager.prototype.use = function use(fulfilled, rejected, options) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected,
synchronous: options ? options.synchronous : false,
runWhen: options ? options.runWhen : null,
});
return this.handlers.length - 1;
};
/**
* 从栈中删除一个拦截器
*
* @param {Number} id 使用use方法给的id来删除
*/
InterceptorManager.prototype.eject = function eject(id) {
// 直接把对应的地方设为null
if (this.handlers[id]) {
this.handlers[id] = null;
}
};
/**
* 迭代已注册的拦截器
*
* 该方法对于跳过任何可能变成"null"的拦截器。
*
* @param {Function} fn 使用fn方法来调用每一个拦截器
*/
InterceptorManager.prototype.forEach = function forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
fn(h);
}
});
};
module.exports = InterceptorManager;
Axios.js
Axios.js 是 /core 目录下的最终出口,也是生成 Axios 类的位置。Axios 的构造函数结构如下:
function Axios(instanceConfig) {
this.defaults;
this.interceptors;
}
Axios.prototype.request;
Axios.prototype.getUri;
Axios.prototype.delete;
Axios.prototype.get;
Axios.prototype.head;
Axios.prototype.options;
Axios.prototype.post;
Axios.prototype.put;
Axios.prototype.patch;
其中最核心的方法也就是 Axios.prototype.request 方法,在该方法中主要执行三步:
- 处理请求拦截器
- 发送请求
- 处理响应拦截器
- 返回响应
源码如下:
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');
var validators = validator.validators;
/**
* 创建一个Axios的新实例
*
* @param {Object} instanceConfig 实例的配置
*/
function Axios(instanceConfig) {
// 存储配置和请求响应拦截器
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager(),
};
}
/**
* 原型上的发送请求方法
*
* @param {Object} config 请求时的配置(已经与默认配置合并)
*/
Axios.prototype.request = function request(config) {
/*eslint no-param-reassign:0*/
// 当config为字符串时,允许axios('example/url'[, config])这种风格的请求
if (typeof config === 'string') {
// 从arguments中取参数
config = arguments[1] || {};
config.url = arguments[0];
} else {
config = config || {};
}
// 合并手动和默认配置
config = mergeConfig(this.defaults, config);
// 设置config上的method属性,优先手动,次之默认,如果都没配置就选get
if (config.method) {
config.method = config.method.toLowerCase();
} else if (this.defaults.method) {
config.method = this.defaults.method.toLowerCase();
} else {
config.method = 'get';
}
// 断言版本问题
var transitional = config.transitional;
if (transitional !== undefined) {
validator.assertOptions(
transitional,
{
silentJSONParsing: validators.transitional(validators.boolean, '1.0.0'),
forcedJSONParsing: validators.transitional(validators.boolean, '1.0.0'),
clarifyTimeoutError: validators.transitional(
validators.boolean,
'1.0.0'
),
},
false
);
}
// 过滤拦截器
// 设一个空数组来保存全部的请求拦截器
var requestInterceptorChain = [];
// 拦截是否异步的标志
var synchronousRequestInterceptors = true;
this.interceptors.request.forEach(function unshiftRequestInterceptors(
interceptor
) {
// 时机不对,不能添加到拦截器链中
if (
typeof interceptor.runWhen === 'function' &&
interceptor.runWhen(config) === false
) {
return;
}
// 只要有一个不是同步,就设为false
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) {
// 把选择适配器发送请求的方法放在chain数组的最后
var chain = [dispatchRequest, undefined];
// 请求拦截器放到数组前面
Array.prototype.unshift.apply(chain, requestInterceptorChain);
// 响应拦截器放到数组后面
chain.concat(responseInterceptorChain);
// 将config作为promise链的第一个
promise = Promise.resolve(config);
// 循环执行promise
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();
// 执行拦截器的fulfilled
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;
};
/**
* @description: 通过config的配置组装url
* @param {Object} config 配置
* @return {String} 返回拼装成的uri
*/
Axios.prototype.getUri = function getUri(config) {
config = mergeConfig(this.defaults, config);
return buildURL(config.url, config.params, config.paramsSerializer).replace(
/^\?/,
''
);
};
// 给每个method添加别名,这四个方法有可能是没有data的
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,
data: (config || {}).data,
})
);
};
}
);
// 下面三个方法有data
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,
})
);
};
});
module.exports = Axios;
总结
本篇分析了 /core 目录下的核心工具方法,逐步接近了最核心的 Axios 类。
下一篇 Axios 源码解析(六):入口文件 来解析入口文件 axios.js,直达最终目标。