import utils from './../utils.js'; import settle from './../core/settle.js'; import transitionalDefaults from '../defaults/transitional.js'; import AxiosError from '../core/AxiosError.js'; import CanceledError from '../cancel/CanceledError.js'; import parseProtocol from '../helpers/parseProtocol.js'; import platform from '../platform/index.js'; import AxiosHeaders from '../core/AxiosHeaders.js'; import {progressEventReducer} from '../helpers/progressEventReducer.js'; import resolveConfig from "../helpers/resolveConfig.js"; const isXHRAdapterSupported = typeof XMLHttpRequest !== 'undefined'; export default isXHRAdapterSupported && function (config) { return new Promise(function dispatchXhrRequest(resolve, reject) { const _config = resolveConfig(config); let requestData = _config.data; const requestHeaders = AxiosHeaders.from(_config.headers).normalize(); let {responseType, onUploadProgress, onDownloadProgress} = _config; let onCanceled; let uploadThrottled, downloadThrottled; let flushUpload, flushDownload; function done() { flushUpload && flushUpload(); // flush events flushDownload && flushDownload(); // flush events _config.cancelToken && _config.cancelToken.unsubscribe(onCanceled); _config.signal && _config.signal.removeEventListener('abort', onCanceled); } let request = new XMLHttpRequest(); request.open(_config.method.toUpperCase(), _config.url, true); // Set the request timeout in MS request.timeout = _config.timeout; function onloadend() { if (!request) { return; } // Prepare the response 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(function _resolve(value) { resolve(value); done(); }, function _reject(err) { reject(err); done(); }, response); // Clean up request request = null; } if ('onloadend' in request) { // Use onloadend if available request.onloadend = onloadend; } else { // Listen for ready state to emulate onloadend 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); }; } // Handle browser request cancellation (as opposed to a manual cancellation) request.onabort = function handleAbort() { if (!request) { return; } reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, config, 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(new AxiosError('Network Error', AxiosError.ERR_NETWORK, config, request)); // Clean up request request = null; }; // Handle timeout 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)); // Clean up request request = null; }; // 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 (onDownloadProgress) { ([downloadThrottled, flushDownload] = progressEventReducer(onDownloadProgress, true)); request.addEventListener('progress', downloadThrottled); } // Not all browsers support upload events if (onUploadProgress && request.upload) { ([uploadThrottled, flushUpload] = progressEventReducer(onUploadProgress)); request.upload.addEventListener('progress', uploadThrottled); request.upload.addEventListener('loadend', flushUpload); } if (_config.cancelToken || _config.signal) { // Handle cancellation // eslint-disable-next-line func-names 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) { _config.signal.aborted ? onCanceled() : _config.signal.addEventListener('abort', onCanceled); } } const protocol = parseProtocol(_config.url); if (protocol && platform.protocols.indexOf(protocol) === -1) { reject(new AxiosError('Unsupported protocol ' + protocol + ':', AxiosError.ERR_BAD_REQUEST, config)); return; } // Send the request request.send(requestData || null); });
时间: 2025-08-08 21:22:16 浏览: 5
<think>好的,我现在需要处理用户关于Axios库中XHRAdapter实现细节的查询。首先,我得回忆一下Axios的基本结构,特别是它在浏览器端使用的适配器。用户提到的是XHRAdapter,也就是基于XMLHttpRequest的适配器,这是Axios在浏览器环境中发送HTTP请求的核心部分。根据用户提供的引用,特别是引用[4]和[5],里面提到了XHRAdapter的代码结构和取消请求的处理。引用[5]详细说明了取消请求的机制,包括使用cancelToken和AbortController。我需要确保在回答中涵盖这些关键点。接下来,我需要整理XHRAdapter的主要流程。这包括创建XMLHttpRequest实例、处理请求配置(如headers、data)、设置事件监听器(如onload、onerror)、取消请求的实现,以及超时处理。同时,用户可能关心如何与拦截器协同工作,这部分在引用[4]中有提到拦截器管理的部分,但用户的问题集中在XHRAdapter本身,所以可能需要简要提及与拦截器的集成。用户可能希望了解XHRAdapter如何将配置转换为实际的HTTP请求,如何处理异步操作,以及错误处理机制。例如,如何将请求数据转换为JSON或FormData,如何设置请求头,特别是Content-Type的处理。此外,取消请求的细节,如使用cancelToken和AbortController,需要详细说明,因为这是Axios的重要特性之一。
还要注意代码示例的正确性,确保使用正确的语法,比如XMLHttpRequest的创建、事件监听的添加,以及Promise的使用。引用[3]中的POST请求示例可以帮助用户理解实际使用中的配置,但这里需要展示适配器内部的实现代码片段。
另外,需要检查是否覆盖了所有关键步骤,比如设置响应类型、处理超时、上传进度事件等。这些细节能帮助用户全面了解XHRAdapter的工作原理。同时,要确保引用的正确性,比如引用[5]中关于取消请求的处理,需要在相应部分添加引用标识。
最后,生成相关问题时要考虑用户可能的后续疑问,比如与其他适配器的比较、取消请求的其他方法、错误处理的最佳实践,以及如何扩展适配器功能。这些问题应该与XHRAdapter的实现细节相关,帮助用户深入理解Axios的内部机制。</think>### Axios XHRAdapter 实现细节解析
Axios 的 `XHRAdapter` 是浏览器端用于发送 HTTP 请求的核心适配器,基于 `XMLHttpRequest` 对象实现。以下是其关键实现细节:
---
#### 1. **适配器入口与初始化**
- **入口函数**:`xhrAdapter` 是一个返回 Promise 的函数,接收 Axios 配置对象 `config`。
```javascript
module.exports = function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
// 初始化逻辑
});
};
```
---
#### 2. **XMLHttpRequest 实例化**
- 创建 `XMLHttpRequest` 对象,并绑定事件监听器:
```javascript
const request = new XMLHttpRequest();
request.open(config.method.toUpperCase(), config.url, true); // 异步模式
```
---
#### 3. **请求配置处理**
- **超时设置**:从 `config.timeout` 读取超时时间。
```javascript
if (config.timeout) {
request.timeout = config.timeout;
}
```
- **请求头设置**:遍历 `config.headers` 设置请求头。
```javascript
Object.keys(config.headers).forEach((header) => {
request.setRequestHeader(header, config.headers[header]);
});
```
- **数据序列化**:根据 `config.transformRequest` 转换请求数据(如 JSON 或 FormData)[^4]。
---
#### 4. **事件监听与响应处理**
- **成功响应**:`onload` 事件处理响应数据。
```javascript
request.onload = function handleLoad() {
const response = {
data: request.response,
status: request.status,
headers: parseHeaders(request.getAllResponseHeaders()),
config: config
};
resolve(response);
};
```
- **错误处理**:`onerror` 和 `ontimeout` 捕获网络错误或超时。
```javascript
request.onerror = function handleError() {
reject(new Error('Network Error'));
};
request.ontimeout = function handleTimeout() {
reject(new Error(`Timeout of ${config.timeout}ms exceeded`));
};
```
---
#### 5. **取消请求实现**
- **CancelToken**:通过 `config.cancelToken` 订阅取消事件。
```javascript
if (config.cancelToken) {
config.cancelToken.subscribe(function onCanceled(cancel) {
if (!request) return;
request.abort(); // 终止请求
reject(cancel);
});
}
```
- **AbortController**:兼容 Fetch API 的取消方式。
```javascript
if (config.signal) {
config.signal.addEventListener('abort', () => {
request.abort();
reject(new Error('Request aborted'));
});
}
```
取消后调用 `done()` 清理订阅[^5]。
---
#### 6. **上传/下载进度监听**
- **进度事件**:通过 `config.onUploadProgress` 和 `config.onDownloadProgress` 暴露进度。
```javascript
if (config.onDownloadProgress) {
request.addEventListener('progress', config.onDownloadProgress);
}
if (config.onUploadProgress && request.upload) {
request.upload.addEventListener('progress', config.onUploadProgress);
}
```
---
#### 7. **请求发送与数据提交**
- **发送数据**:调用 `request.send` 并传入序列化后的数据。
```javascript
request.send(config.data || null);
```
---
### 关键设计特点
1. **Promise 封装**:将 XHR 的异步回调转换为 Promise,便于链式调用[^2]。
2. **配置驱动**:所有行为(如超时、头信息、取消)均通过 `config` 控制。
3. **模块化清理**:在请求完成或取消时移除事件监听和订阅,避免内存泄漏[^5]。
---
阅读全文