之前我们已经了解到,axios源码中的AJAX封装是通过适配器进行调用的,而他的返回值一定是一个Promise对象,这个Promise对象会作为dispatRequest的返回值,放入执行链中跟拦截器一起进行链式执行和调用。对应AJAX封装这部分的源码,知识点相对比较散,因此直接上源码,对其中的一些部分做了注释进行解读
module.exports = function xhrAdapter(config) {
// 接收axios传入的参数
return new Promise(function dispatchXhrRequest(resolve, reject) {
// 对配置参数进行重命名,便于我们处理
var requestData = config.data;
var requestHeaders = config.headers;
var responseType = config.responseType;
// FormData对象用以将数据编译成键值对,以便用XMLHttpRequest来发送数据。其主要用于发送表单数据,
// 但亦可用于发送带键数据(keyed data),而独立于表单使用。如果表单enctype属性设为multipart/form-data ,
// 则会使用表单的submit()方法来发送数据,从而,发送数据具有同样形式。
if (utils.isFormData(requestData)) {
// 检验是否为表单类型,如果是的话取消请求内容的类型,让浏览器自行设置
delete requestHeaders['Content-Type']; // Let the browser set it
}
// XMLHttpRequest(XHR)对象用于与服务器交互。通过 XMLHttpRequest 可以在不刷新页面的情况下请求特定 URL,获取数据。
// 新建XMLHttpRequest对象
var request = new XMLHttpRequest();
// HTTP basic authentication
if (config.auth) {
// http auth是一种基础的用户验证,原理是将用户名:密码base64加密后放在http的请求头部Authorization 发给服务器
var username = config.auth.username || '';
var password = config.auth.password ? unescape(encodeURIComponent(config.auth.password)) : '';
requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password); //加密,创建一个 base-64 编码的字符串
}
// 恢复完整的url
var fullPath = buildFullPath(config.baseURL, config.url);
//带参数进行拼接,(如果是get请求,url后面直接加params)
// 打开连接
request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);
// Set the request timeout in MS
// 设置超时
request.timeout = config.timeout;
function onloadend() {
// 注意这部分被定义会最后执行
if (!request) {
return;
}
// Prepare the response
// 要求返回的值如果不指定,是text或json,返回文本格式,否则返回其他类型(根据responsetype类型)
var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null;
var responseData = !responseType || responseType === 'text' || responseType === 'json' ?
request.responseText : request.response;
// 整合返回数据
var response = {
data: responseData, //返回的数据
status: request.status, //返回的状态码
statusText: request.statusText, //返回的状态文本信息
headers: responseHeaders, //之前拿到的请求头
config: config, //传进来的配置
request: request //XMLHttpRequest
};
//验证状态码或有效状态数据是否有效并决定回调函数的执行
// 用于自定义如何处理各种响应码对于的执行方法
settle(resolve, reject, response);
// Clean up request
request = null;
}
// 表示在引发loadend事件时(在加载资源的进度停止时)要调用的代码
if ('onloadend' in request) { //适配不同浏览器
// Use onloadend if available
request.onloadend = onloadend;
} else {
// Listen for ready state to emulate onloadend
//通过监听readystate来模拟onloadend事件
// 每当 readyState 属性改变时,就会调用该函数。
// 每次状态改变都会调用这个函数
request.onreadystatechange = function handleLoad() {
// 如果请求没有完成,直接返回,等待下一次运行函数检测
if (!request || request.readyState !== 4) {
return;
}
// readState:
// 0: 请求未初始化
// 1: 服务器连接已建立
// 2: 请求已接收
// 3: 请求处理中
// 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
//兼容了file协议的情况,请求使用file: protocol,大多数浏览器将返回状态为0,即使这是一个成功的请求
if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {
return;
}
// readystate handler is calling before onerror or ontimeout handlers,
// so we should call onloadend on the next 'tick'
// readystate处理程序在onerror或ontimeout处理程序之前调用,因此我们应该在下一个 'tick' 上调用onloadend
setTimeout(onloadend);
};
}
// 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() {
var timeoutErrorMessage = 'timeout of ' + config.timeout + 'ms exceeded';
//可以配置超时的报错信息
if (config.timeoutErrorMessage) {
timeoutErrorMessage = config.timeoutErrorMessage;
}
// 直接调用失败的回调抛出错误
reject(createError(
timeoutErrorMessage,
config,
config.transitional && config.transitional.clarifyTimeoutError ? 'ETIMEDOUT' : '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.
//阻止跨站请求伪造,,只支持react-native 和web环境
// XSRF(CSRF) 攻击的原理是攻击者能猜测出所有的需要提交的内容以及类型,
// 所以所有的解决方案共同出发点就是加一个攻击者也不知道随机值发送给后端验证就可以防范。
if (utils.isStandardBrowserEnv()) {
//检验web环境
// Add xsrf header
//检验证书或者cookie
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) {
// toLowerCase() 方法用于把字符串转换为小写。
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
// 这里的凭证是一个布尔值,来指定跨域Access-Control请求是否带有授权信息,比如cookie或header头
if (!utils.isUndefined(config.withCredentials)) {
request.withCredentials = !!config.withCredentials;
}
// Add responseType to request if needed
if (responseType && responseType !== 'json') {
request.responseType = config.responseType;
}
// Handle progress if needed
//加载时执行的事件
if (typeof config.onDownloadProgress === 'function') {
request.addEventListener('progress', config.onDownloadProgress);
}
// Not all browsers support upload events
//XHR中的上传事件
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;
}
// 调用XMLHttpRequest取消请求的方法
request.abort();
reject(cancel);
// Clean up request
request = null;
});
}
// 没有返回数据默认返回null
if (!requestData) {
requestData = null;
}
// Send the request
request.send(requestData);
});
};