上篇 Axios 源码解析(四):核心工具方法(1) 解析了 default.js
和 /cancel
目录下的源码,下面继续解析 /cancel
目录下核心工具方法的代码。
在https://github.com/MageeLin/axios-source-code-analysis 中的 analysis
分支可以看到当前已解析完的文件。
总览 在《Axios 源码解析(一):模块分解》 中已经分析过,/core
目录中包含如下这些文件:
1 2 3 4 5 6 7 8 9 10 11 ├─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
必须为一个绝对地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var isAbsoluteURL = require ('../helpers/isAbsoluteURL' );var combineURLs = require ('../helpers/combineURLs' );module .exports = function buildFullPath (baseURL, requestedURL ) { if (baseURL && !isAbsoluteURL(requestedURL)) { return combineURLs(baseURL, requestedURL); } return requestedURL; };
enhanceError.js enhanceError
方法就是单纯为了升级 Error
对象,给 Axios
生成的错误对象添加 config
、code
、request
和 response
属性
1 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 module .exports = function enhanceError (error, config, code, request, response ) { error.config = config; if (code) { error.code = code; } error.request = request; error.response = response; error.isAxiosError = true ; error.toJSON = function toJSON ( ) { return { message: this .message, name: this .name, description: this .description, number: this .number, fileName: this .fileName, lineNumber: this .lineNumber, columnNumber: this .columnNumber, stack: this .stack, config: this .config, code: this .code, }; }; return error; };
createError.js enhanceError
方法增强的就是 createError
方法生成的对象,Error
对象刚生成的时候只有一个 message
属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 var enhanceError = require ('./enhanceError' );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
掉。
1 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 var createError = require ('./createError' );module .exports = function settle (resolve, reject, response ) { var validateStatus = response.config.validateStatus; if (!response.status || !validateStatus || validateStatus(response.status)) { resolve(response); } else { reject( createError( 'Request failed with status code ' + response.status, response.config, null , response.request, response ) ); } };
mergeConfig.js 由于 Axios
中有默认配置、用户默认配置和用户自定义配置多种 config
,所以 mergeConfig
就是用专门的规则来合并这些配置的,会返回一个新的合并后配置对象。
merge
的规则比较复杂,针对不同的属性需要有不同的合并策略:
1 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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 'use strict' ;var utils = require ('../utils' );module .exports = function mergeConfig (config1, config2 ) { 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 ) { 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; } 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]); } }); 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; };
transformData
方法是用来支持 transformRequest
和 transformResponse
的,transformRequest
允许在向服务器发送请求前修改请求数据,transformResponse
允许在传递结果给 then/catch
前修改响应数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 var utils = require ('./../utils' );var defaults = require ('./../defaults' );module .exports = function transformData (data, headers, fns ) { var context = this || defaults; utils.forEach(fns, function transform (fn ) { data = fn.call(context, data, headers); }); return data; };
dispatchRequest.js dispatchRequest
方法最直接的执行发送请求的方法,它分为如下几步:
根据配置转换请求对象
整理请求头
选择一个适配器并发送请求
根据配置转换响应对象
返回响应
1 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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 var utils = require ('./../utils' );var transformData = require ('./transformData' );var isCancel = require ('../cancel/isCancel' );var defaults = require ('../defaults' );function throwIfCancellationRequested (config ) { if (config.cancelToken) { config.cancelToken.throwIfRequested(); } } module .exports = function dispatchRequest (config ) { throwIfCancellationRequested(config); config.headers = config.headers || {}; config.data = transformData.call( config, config.data, config.headers, config.transformRequest ); 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); response.data = transformData.call( config, response.data, response.headers, config.transformResponse ); return response; }, function onAdapterRejection (reason ) { if (!isCancel(reason)) { throwIfCancellationRequested(config); if (reason && 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
迭代每一个拦截器。
1 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 var utils = require ('./../utils' );function InterceptorManager ( ) { this .handlers = []; } 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 ; }; InterceptorManager.prototype.eject = function eject (id ) { if (this .handlers[id]) { this .handlers[id] = null ; } }; 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
的构造函数结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 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
方法,在该方法中主要执行三步:
处理请求拦截器
发送请求
处理响应拦截器
返回响应
源码如下:
1 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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 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;function Axios (instanceConfig ) { this .defaults = instanceConfig; this .interceptors = { request: new InterceptorManager(), response: new InterceptorManager(), }; } Axios.prototype.request = function request (config ) { if (typeof config === 'string' ) { config = arguments [1 ] || {}; config.url = arguments [0 ]; } else { config = config || {}; } config = mergeConfig(this .defaults, config); 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 ; } 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.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.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
,直达最终目标。