axios网络请求框架源码解析
Posted Sahadev_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了axios网络请求框架源码解析相关的知识,希望对你有一定的参考价值。
早期axios0.1.0版本做了对IE浏览器与包含XmlHttpRequest的浏览器的支持。并且做了对请求参数拼接、Json对象序列化等基本功能。
到0.19.0版本时,内部请求已经变为了在Node环境下与主流浏览器的支持,其中Node环境下支持http请求与https请求。并且支持取消、拦截。
Axios执行开始之初,首先执行createInstance
方法,createInstance
方法用来创建一个新的Axios实例。但这里奇怪的是,返回的示例并不是真正new出来的实例,而是一个幻影。实际在执行时,内部代码的指向还是内部的Axios对象。Axios内部使用了wrap来表示各个方法,可能真的是为了将真实的Axios实例隐藏。这么做作用在于防止外部修改内部的方法,做好了封装和防护。
function createInstance(defaultConfig)
var context = new Axios(defaultConfig);
var instance = bind(Axios.prototype.request, context);
// Copy axios.prototype to instance
utils.extend(instance, Axios.prototype, context);
// Copy context to instance
utils.extend(instance, context);
return instance;
不过,外部代码还是可以访问到内部的Axios实例的,在创建了幻影之后,继续执行以下代码:
// Expose Axios class to allow class inheritance
axios.Axios = Axios;
// Factory for creating new instances
axios.create = function create(instanceConfig)
return createInstance(mergeConfig(axios.defaults, instanceConfig));
;
// Expose Cancel & CancelToken
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');
// Expose all/spread
axios.all = function all(promises)
return Promise.all(promises);
;
axios.spread = require('./helpers/spread');
module.exports = axios;
// Allow use of default import syntax in TypeScript
module.exports.default = axios;
好,这里是外部的一些构造。其实我们拿到axios对象时就可以发起请求了。接下来我们通过一个使用示例来说明请求过程。以下是我们的request请求层示例:
// 使用示例,业务网络层request.js
const service = axios.create(
baseURL: BASE_API,
timeout: TIMEOUT
);
service.interceptors.request.use(
config =>
return config;
,
error =>
Promise.reject(error);
);
service.interceptors.response.use(
response =>
return Promise.reject(response);
,
error =>
return Promise.reject(error);
);
export default service;
假设我们的业务网络层是上面的用法,然后在具体的业务代码处通过get方法发起了一次业务请求:
axios.get('/get/server').then(function (response)
).catch(function (err)
);
当业务请求代码发起时,具体执行的是lib/core/Axios.js中的request方法:
// lib/core/Axios.js
Axios.prototype.request = function request(config)
/*eslint no-param-reassign:0*/
// Allow for axios('example/url'[, config]) a la fetch API
if (typeof config === 'string')
config = arguments[1] || ;
config.url = arguments[0];
else
config = config || ;
config = mergeConfig(this.defaults, config);
config.method = config.method ? config.method.toLowerCase() : 'get';
// Hook up interceptors middleware
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;
;
request方法的参数为一个config对象,而这个对象是由以下信息组成的:
method: 'get',
url: '/get/server'
紧接着会通过mergeConfig方法将自定义的config对象与默认的config对象进行合并:
// lib/core/Axios.js
config = mergeConfig(this.defaults, config);
而这里的this.defaults实际内部如下:
// lib/defaults.js
var defaults =
adapter: getDefaultAdapter(),
// 默认的请求转换
transformRequest: [function transformRequest(data, headers)
normalizeHeaderName(headers, 'Accept');
normalizeHeaderName(headers, 'Content-Type');
if (utils.isFormData(data) ||
utils.isArrayBuffer(data) ||
utils.isBuffer(data) ||
utils.isStream(data) ||
utils.isFile(data) ||
utils.isBlob(data)
)
return data;
if (utils.isArrayBufferView(data))
return data.buffer;
if (utils.isURLSearchParams(data))
setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');
return data.toString();
if (utils.isObject(data))
setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
return JSON.stringify(data);
return data;
],
// 默认的相应转换,自定义转换方法,Json解析
transformResponse: [function transformResponse(data)
/*eslint no-param-reassign:0*/
if (typeof data === 'string')
try
data = JSON.parse(data);
catch (e) /* Ignore */
return data;
],
/**
* A timeout in milliseconds to abort a request. If set to 0 (default) a
* timeout is not created.
*/
timeout: 0,
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
validateStatus: function validateStatus(status)
return status >= 200 && status < 300;
;
总之是一些待会会用到的默认配置。合并的机制是优先取自定义的配置,再取默认配置,组成一个新的合并对象。
request的方法继续向下,到了关键的地方:
// lib/core/Axios.js
// Hook up interceptors middleware
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);
);
由于咱们的业务网络层使用了interceptors.request.use
,interceptors.response.use
这样的字样,所以我们应该看看对应的use方法做了什么:
// lib/core/InterceptorManager.js
InterceptorManager.prototype.use = function use(fulfilled, rejected)
this.handlers.push(
fulfilled: fulfilled,
rejected: rejected
);
return this.handlers.length - 1;
;
而InterceptorManager的构造方法如下:
// lib/core/InterceptorManager.js
function InterceptorManager()
this.handlers = [];
InterceptorManager在构造之初内部只有一个数组属性handlers,所以当业务网络层使用use方法时,会将对应的fulfilled, rejected组成一个新的对象Push到这个数组中。所以再回来forEach的地方:
// lib/core/InterceptorManager.js
InterceptorManager.prototype.forEach = function forEach(fn)
utils.forEach(this.handlers, function forEachHandler(h)
if (h !== null)
fn(h);
);
;
forEach内部仅仅将handlers遍历,将数组中的对象通过forEach的回调方法传出,所以在Axios的request方法内,就是将业务网络层所定义的request interceptors与response interceptors压入数组chain中。所以在两个forEach执行完之后,数组chain的执行如下:
chain = [request.interceptors.success, request.interceptors.fail, dispatchRequest, undefined, response.interceptors.success, response.interceptors.fail]
然后就是根本性的一环:
// lib/core/Axios.js
while (chain.length)
promise = promise.then(chain.shift(), chain.shift());
当while方法执行时,开始触发chain中所塞进去的各个方法。在这里首先执行的是request.interceptors.success,也就是业务网络层所定义的请求拦截层。在我们的示例中什么处理都没做,于是再执行dispatchRequest。这个dispatchRequest可大有来头,是请求的核心:
// lib/core/dispatchRequest.js
function dispatchRequest(config)
throwIfCancellationRequested(config);
// Support baseURL config
if (config.baseURL && !isAbsoluteURL(config.url))
config.url = combineURLs(config.baseURL, config.url);
// 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 ||
);
// 为什么要删除Header中的请求方式?
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);
);
;
dispatchRequest方法执行了以下事情:
- 1.校验这次的请求是否已取消。
- 2.构造完整的请求Url。
- 3.确认header存在。
- 4.转换请求数据与header。转换方法在这里也可以通过config自定义。默认转换为Json串。
- 5.合并config.headers。
- 6.删除config.headers中请求方法所对应的值。
- 7.获取对应的网络请求适配器并发起请求。默认的adapter主要有两种:1.浏览器环境下为XMLHttpRequest. 2.Node环境下为https库或者http库,这里还支持自定义网路请求框架。
在这里代码分析我们假设运行环境为浏览器,那我们进入浏览器环境的dispatchRequest方法:
// lib/adapters/xhr.js
function xhrAdapter(config)
return new Promise(function dispatchXhrRequest(resolve, reject)
var requestData = config.data;
var requestHeaders = config.headers;
if (utils.isFormData(requestData))
delete requestHeaders['Content-Type']; // Let the browser set it
var request = new XMLHttpRequest();
// HTTP basic authentication
if (config.auth)
var username = config.auth.username || '';
var password = config.auth.password || '';
requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password);
// 这里的method从哪来的?
request.open(config.method.toUpperCase(), buildURL(config.url, config.params, config.paramsSerializer), true);
// Set the request timeout in MS
request.timeout = config.timeout;
// Listen for ready state
request.onreadystatechange = function handleLoad()
if (!request || request.readyState !== 4)
return;
// The request errored out and we didn't get a response, this will be
// handled by onerror instead
// With one exception: request that using file: protocol, most browsers
// will return status as 0 even though it's a successful request
if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0))
return;
// Prepare the response
var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null;
var responseData = !config.responseType || config.responseType === 'text' ? request.responseText : request.response;
var response =
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config: config,
request: request
;
settle(resolve, reject, response);
// Clean up request
request = null;
;
// Handle browser request cancellation (as opposed to a manual cancellation)
request.onabort = function handleAbort()
if (!request)
return;
reject(createError('Request aborted', config, 'ECONNABORTED', request));
// Clean up request
request = null;
;
// Handle low level network errors
request.onerror = function handleError()
// Real errors are hidden from us by the browser
// onerror should only fire if it's a network error
reject(createError('Network Error', config, null, request));
// Clean up request
request = null;
;
// Handle timeout
request.ontimeout = function handleTimeout()
reject(createError('timeout of ' + config.timeout + 'ms exceeded', config, 'ECONNABORTED',
request));
// Clean up request
request = null;
;
// Add xsrf header
// This is only done if running in a standard browser environment.
// Specifically not if we're in a web worker, or react-native.
if (utils.isStandardBrowserEnv())
var cookies = require('./../helpers/cookies');
// Add xsrf header
var xsrfValue = (config.withCredentials || isURLSameOrigin(config.url)) && config.xsrfCookieName ?
cookies.read(config以上是关于axios网络请求框架源码解析的主要内容,如果未能解决你的问题,请参考以下文章