axios源码分析
源码目录分析
- axios.js: axios导出的文件入口
- Axios.js: 添加原型方法
- default.js: 默认的axios配置
- adapters:适配浏览器和非浏览器请求
- cancel:取消请求类
axios的工作流程
axios的工作原理
1. axios
// axios 和 instance 都会调用createInstance
function createInstance(defaultConfig) {
var context = new Axios(defaultConfig);
// 绑定Axios原型上的方法request
// Axios.prototype.request.bind(context), 返回的是方法。
var instance = bind(Axios.prototype.request, context);
// 将Axios上原型的方法复制到instance上,使得axios能调用get,post,put,delete等方法 axios.get()
utils.extend(instance, Axios.prototype, context);
// Copy context to instance
utils.extend(instance, context);
return instance;
}
// Create the default instance to be exported
var axios = createInstance(defaults);
// Expose Axios class to allow class inheritance
axios.Axios = Axios;
// 同样支持axios.create(),创建instance,跟axios没有太大区别,都是通过调用createInstance来的,但是instance没有axios后面添加的cancel,cancelToken等方法
axios.create = function create(instanceConfig) {
return createInstance(mergeConfig(axios.defaults, instanceConfig));
};
// 为axios添加新的属性方法,取消请求。
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');
- 无论是``axios
还是
instance都会调用
createInstance函数,构造
Axios的实例,但
axios从本质上来说还是一个函数,通过
extend,bind将
Axios`上复制所有的原型扩展方法。 axios
和instance
是有区别的,axios
包含了后面扩展的Cancel
、CancelToken
等方法。- 通过这种方式,在
createInstance
返回的axios
函数的属性上,就已经挂在了Axios
原型上的方法和属性了。
2. Axios 核心类
function Axios(instanceConfig) {
this.defaults = instanceConfig;
// 创建两个拦截器。
this.interceptors = {
request: new InterceptorManager(), // 数组存放收集
response: new InterceptorManager() // 数组存放收集
};
}
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
});
return this.handlers.length - 1;
};
- 收集请求拦截,返回拦截,支持多个拦截操作,触发时会一并进行调用。调用的顺序有所不同,请求拦截是
unshift
插入,倒序执行,返回拦截是push
插入,顺序执行。
// 核心,实现请求
Axios.prototype.request = function request(config) {
/*eslint no-param-reassign:0*/
if (typeof config === 'string') { // axios('/xxx') 默认是get方法
config = arguments[1] || {};
config.url = arguments[0];
} else {
// config是个对象
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';
}
// Hook up interceptors middleware
var chain = [dispatchRequest, undefined];
var promise = Promise.resolve(config);
// 拦截器会在then返回之前执行
// 请求拦截器是倒序执行的,unshift
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
// 返回拦截器是正序执行的,push
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
是个字符串,表现形式为axios('/xxx')
, 也可以是object
, 会去合并mergeConfig(this.defaults, config)
默认值。- 统一配置请求方式为
toLowerCase
, 默认为get
方式 - 调用
interceptors
存放的方法 chain.length
不为0
, 支持链式调用,最终返回promise
3 dispatchRequest
module.exports = function dispatchRequest(config) {
throwIfCancellationRequested(config);
// Ensure headers exist
config.headers = config.headers || {};
// Transform request data
// data, headers 作为参数放到 transformRequest 中执行
config.data = transformData(
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];
}
);
// 判断出是node环境还是浏览器环境
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
主要做了哪些,将data、headers
放入到transformData
中执行,再将headers
扁平化处理transformRequest
函数数组,对data
数据进行转换adapter
判断出是node
坏境还是浏览器环境,adapter.then()
, 将返回response
数据。
// ajax请求,http请求
function getDefaultAdapter() {
var adapter;
if (typeof XMLHttpRequest !== 'undefined') {
// For browsers use XHR adapter
adapter = require('./adapters/xhr');
} else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
// For node use HTTP adapter
adapter = require('./adapters/http');
}
return adapter;
}
4 xhr
module.exports = 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 ? unescape(encodeURIComponent(config.auth.password)) : '';
requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password);
}
// 合并请求连接,可以通过{baseURL: 'xxx'}或者axios.defaults.baseURL = 'https://xxx';设置
var fullPath = buildFullPath(config.baseURL, config.url);
request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);
// Set the request timeout in MS
request.timeout = config.timeout;
// Listen for ready state
// // onreadystatechange的状态码
// 0: 初始化, XMLHttpRequest对象还没有完成初始化
// 1: 载入, XMLHttpRequest对象开始发送请求
// 2: 载入完成, XMLHttpRequest对象的请求发送完成
// 3: 解析, XMLHttpRequest对象开始读取服务器的响应
// 4: 完成, XMLHttpRequest对象读取服务器响应结束
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
};
// 当返回的status状态码在[200, 300)之间,返回resolve(response)
//default.js中 validateStatus: function validateStatus(status) {
// return status >= 200 && status < 300;
// }
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;
};
// 处理网络错误问题
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;
};
// 处理请求超时问题。
request.ontimeout = function handleTimeout() {
var timeoutErrorMessage = 'timeout of ' + config.timeout + 'ms exceeded';
if (config.timeoutErrorMessage) {
timeoutErrorMessage = config.timeoutErrorMessage;
}
reject(createError(timeoutErrorMessage, 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()) {
// Add xsrf header
var xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath)) && config.xsrfCookieName ?
cookies.read(config.xsrfCookieName) :
undefined;
if (xsrfValue) {
requestHeaders[config.xsrfHeaderName] = xsrfValue;
}
}
// Add headers to the request
if ('setRequestHeader' in request) {
utils.forEach(requestHeaders, function setRequestHeader(val, key) {
if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') {
// Remove Content-Type if data is undefined
delete requestHeaders[key];
} else {
// Otherwise add header to the request
request.setRequestHeader(key, val);
}
});
}
// Add withCredentials to request if needed
if (!utils.isUndefined(config.withCredentials)) {
request.withCredentials = !!config.withCredentials;
}
// Add responseType to request if needed
if (config.responseType) {
try {
request.responseType = config.responseType;
} catch (e) {
// Expected DOMException thrown by browsers not compatible XMLHttpRequest Level 2.
// But, this can be suppressed for 'json' type as it can be parsed by default 'transformResponse' function.
if (config.responseType !== 'json') {
throw e;
}
}
}
// Handle progress if needed
if (typeof config.onDownloadProgress === 'function') {
request.addEventListener('progress', config.onDownloadProgress);
}
// Not all browsers support upload events
if (typeof config.onUploadProgress === 'function' && request.upload) {
request.upload.addEventListener('progress', config.onUploadProgress);
}
// 取消请求。
if (config.cancelToken) {
// Handle cancellation
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}
request.abort();
reject(cancel);
// Clean up request
request = null;
});
}
if (!requestData) {
requestData = null;
}
// Send the request
request.send(requestData);
});
};
-
创建
new XMLHttpRequest()
实例 -
合并请求连接,可以通过{baseURL: 'xxx'}或者axios.defaults.baseURL = 'https://xxx';设置
-
监听
onreadystatechange
变化,-
0: 初始化, XMLHttpRequest对象还没有完成初始化
-
1: 载入, XMLHttpRequest对象开始发送请求
-
2: 载入完成, XMLHttpRequest对象的请求发送完成
-
3: 解析, XMLHttpRequest对象开始读取服务器的响应
-
4: 完成, XMLHttpRequest对象读取服务器响应结束
-
-
收集
response
返回数据,执行settle
函数,调用validateStatus(status)
判断是否在[200, 300)
,成功状态后resolve(response)
返回数据。
结合代码
<!DOCTYPE html>
<html>
<head>
<title>axios</title>
</head>
<body>
<div>
<button onclick="handleGet()">get</button>
<button onclick="handlePost()">post</button>
</div>
<script src="../源码/axios/dist/axios.js"></script>
<script>
axios.defaults.baseURL = 'http://localhost:8081';
axios.interceptors.request.use((config) => {
console.log('request1')
return config
})
axios.interceptors.request.use((config) => {
console.log('request2')
return config
})
axios.interceptors.response.use((response) => {
console.log('response1')
return response
})
axios.interceptors.response.use((response) => {
console.log('response2')
return response
})
function handleGet(params) {
axios.get('/list').then(json => {
console.log(json);
})
}
function handlePost(params) {
console.log('handlePost')
}
// 返回的顺序
// request2
// request1
// response1
// response2
// {data: {…}, status: 200, statusText: "OK", headers: {…}, config: {…}, …}
</script>
</body>
</html>
5 取消请求
function CancelToken(executor) {
if (typeof executor !== 'function') {
throw new TypeError('executor must be a function.');
}
var resolvePromise;
// 创建了内置的promise对象,将resovle控制放在executor中去控制
//eg: let resolveHandle;
// new Promise((resolve) => {
// resolveHandle = resolve;
// }).then((val) => {
// console.log('resolve', val);
// });
// resolveHandle('ok');
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
});
var token = this;
executor(function cancel(message) {
if (token.reason) {
// Cancellation has already been requested
return;
}
token.reason = new Cancel(message);
resolvePromise(token.reason);
});
}
cancelToken
主要做了什么事情,内置创建了promise
对象,通过resolvePromise=resolve
,将控制放在executor
中去控制,config.cancelToken.promise.then
在异步中去取消请求
if (config.cancelToken) {
// Handle cancellation
// abort方法只会对未进行相应的请求进行取消,已响应的请求执行了也不会有什么作用
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}
request.abort();
reject(cancel);
// Clean up request
request = null;
});
}
cancelToken
只会对未进行相应的请求进行取消,已响应的请求执行了也不会有什么作用。
let CancelToken = axios.CancelToken;
let map = new Map()
axios.defaults.baseURL = 'http://localhost:8081';
axios.interceptors.request.use((config) => {
const f = map.get(config.url)
if (f) {
f()
map.delete(config.url)
}
config.cancelToken = new CancelToken(function executor(c) {
map.set(config.url, c)
})
return config
})
axios.interceptors.response.use((response) => {
const f = map.get(config.url)
if (f) {
map.delete(config.url)
}
return response
})
function handleGet1(params) {
axios.get('/cancel-list1').then(json => {
console.log(json)
});
}
function handleGet2(params) {
axios.get('/cancel-list2').then(json => {
console.log(json)
});
}
function handlePost(params) {
console.log('handlePost')
}
function handleCancel() {
console.log(123);
cancel()
}
总结
-
axios
是一个基于promise
的Http
库,在浏览器环境使用XHR
,在node
环境中使用http
模块发送请求,在axios
,可以对请求,返回进行拦截,支持链式拦截,请求拦截为倒序拦截,使用unshift
往前插入数据,返回拦截使用push
方法,正常拦截。 -
取消请求的是一个异步分离的设计方案,利用
promise
的异步效果,通过切换promise
的状态,从而达到异步取消请求的实现。 -
axios
和axios.create
的差别是增加了cancelToken
等方法。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!