上一节中的 dispatchRequest
核心模块是由 adapter
执行的。
本章,以 XMLHttpRequest
为例,来说明下浏览器端的 adapter
核心实现:
js
// 转换上传下载进度
function progressEventReducer(listener, isDownloadStream) {
let bytesNotified = 0;
const _speedometer = speedometer(50, 250);
return e => {
const loaded = e.loaded;
const total = e.lengthComputable ? e.total : undefined;
const progressBytes = loaded - bytesNotified;
const rate = _speedometer(progressBytes);
const inRange = loaded <= total;
bytesNotified = loaded;
const data = {
loaded,
total,
progress: total ? (loaded / total) : undefined,
bytes: progressBytes,
rate: rate ? rate : undefined,
estimated: rate && total && inRange ? (total - loaded) / rate : undefined,
event: e
};
data[isDownloadStream ? 'download' : 'upload'] = true;
listener(data);
};
}
// 是否浏览器端
const isXHRAdapterSupported = typeof XMLHttpRequest !== 'undefined';
export default isXHRAdapterSupported && function (config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
let requestData = config.data;
const requestHeaders = AxiosHeaders.from(config.headers).normalize();
const responseType = config.responseType;
let onCanceled;
// 响应结束时,解绑“取消监听”
function done() {
if (config.cancelToken) {
config.cancelToken.unsubscribe(onCanceled);
}
if (config.signal) {
config.signal.removeEventListener('abort', onCanceled);
}
}
// 如果data是FormData类型,就将请求头删掉。浏览器会自动设置。
if (utils.isFormData(requestData) && (platform.isStandardBrowserEnv || platform.isStandardBrowserWebWorkerEnv)) {
requestHeaders.setContentType(false);
}
let request = new XMLHttpRequest();
// 根据config.auth转换Authorization请求头
if (config.auth) {
const username = config.auth.username || '';
const password = config.auth.password ? unescape(encodeURIComponent(config.auth.password)) : '';
requestHeaders.set('Authorization', 'Basic ' + btoa(username + ':' + password));
}
const fullPath = buildFullPath(config.baseURL, config.url);
request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);
// 设置超时时间
request.timeout = config.timeout;
function onloadend() {
if (!request) {
return;
}
// 获取响应头
const responseHeaders = AxiosHeaders.from(
'getAllResponseHeaders' in request && request.getAllResponseHeaders()
);
const responseData = !responseType || responseType === 'text' || responseType === 'json' ?
request.responseText : request.response;
// 对响应的数据 作进一步处理
const response = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config,
request
};
// settle函数会根据response中的status与validateStatus 作一轮比较 判断结果是resolve或reject
settle(function _resolve(value) {
resolve(value);
done();
}, function _reject(err) {
reject(err);
done();
}, response);
// 防止内存泄漏
request = null;
}
if ('onloadend' in request) {
// 较新版本的浏览器支持onloadend
request.onloadend = onloadend;
} else {
// 兼容低版本浏览器onreadystatechange
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;
}
// readystate handler is calling before onerror or ontimeout handlers,
// so we should call onloadend on the next 'tick'
setTimeout(onloadend);
};
}
// 监听请求中止(请求取消、或用户手动取消)
request.onabort = function handleAbort() {
if (!request) {
return;
}
reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, config, request));
request = null;
};
// 监听错误
request.onerror = function handleError() {
reject(new AxiosError('Network Error', AxiosError.ERR_NETWORK, config, request));
request = null;
};
// 监听超时
request.ontimeout = function handleTimeout() {
let timeoutErrorMessage = config.timeout ? 'timeout of ' + config.timeout + 'ms exceeded' : 'timeout exceeded';
const transitional = config.transitional || transitionalDefaults;
if (config.timeoutErrorMessage) {
timeoutErrorMessage = config.timeoutErrorMessage;
}
reject(new AxiosError(
timeoutErrorMessage,
transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED,
config,
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 (platform.isStandardBrowserEnv) {
// Add xsrf header
const xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath))
&& config.xsrfCookieName && cookies.read(config.xsrfCookieName);
if (xsrfValue) {
requestHeaders.set(config.xsrfHeaderName, xsrfValue);
}
}
// Remove Content-Type if data is undefined
requestData === undefined && requestHeaders.setContentType(null);
// Add headers to the request
if ('setRequestHeader' in request) {
utils.forEach(requestHeaders.toJSON(), function setRequestHeader(val, key) {
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 (responseType && responseType !== 'json') {
request.responseType = config.responseType;
}
// Handle progress if needed
if (typeof config.onDownloadProgress === 'function') {
request.addEventListener('progress', progressEventReducer(config.onDownloadProgress, true));
}
// Not all browsers support upload events
if (typeof config.onUploadProgress === 'function' && request.upload) {
request.upload.addEventListener('progress', progressEventReducer(config.onUploadProgress));
}
if (config.cancelToken || config.signal) {
// 监听用户手动取消
onCanceled = cancel => {
if (!request) {
return;
}
reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel);
request.abort();
request = null;
};
config.cancelToken && config.cancelToken.subscribe(onCanceled);
if (config.signal) {
// abortController.abort()有可能被使用者提前调用触发
config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
}
}
const protocol = parseProtocol(fullPath);
if (protocol && platform.protocols.indexOf(protocol) === -1) {
reject(new AxiosError('Unsupported protocol ' + protocol + ':', AxiosError.ERR_BAD_REQUEST, config));
return;
}
// 发送请求
request.send(requestData || null);
});
}
XMLHttpRequest
下的 adapter
主要有以下几方面:
xhr
请求的封装、以及相关事件的监听xhr.open()
xhr.timeout
xhr.onloadend
或xhr.onreadystatechange
xhr.onabort
xhr.onerror
xhr.ontimeout
xhr.send()
- 上传、下载进度监听
xhr.upload.onprogress
xhr.onprogress
- 取消请求
cancelToken
signal
XSRF
防范
当我们需要配置自定义适配器的时候,就可以根据以上几个方面的配置,进行查漏补缺。
接下来的一节,我们再重点论述一下取消请求的实现。