Ajax 异步请求方案详解
Ajax(Asynchronous JavaScript and XML)是一种无需重新加载整个网页的情况下,能够更新部分网页的技术。下面我将详细讲解使用原生XMLHttpRequest对象实现Ajax请求的方案。
一、XMLHttpRequest 对象基础
1. 创建XMLHttpRequest对象
// 兼容性处理
function createXHR() {
if (typeof XMLHttpRequest !== 'undefined') {
return new XMLHttpRequest();
} else if (typeof ActiveXObject !== 'undefined') {
// 兼容旧版IE
var versions = [
'MSXML2.XMLHttp.6.0',
'MSXML2.XMLHttp.3.0',
'MSXML2.XMLHttp'
];
for (var i = 0; i < versions.length; i++) {
try {
return new ActiveXObject(versions[i]);
} catch (e) {
// 继续尝试下一个版本
}
}
}
throw new Error('浏览器不支持XMLHttpRequest');
}
// 使用
const xhr = createXHR();
2. 完整的请求流程
function ajaxRequest(config) {
const {
method = 'GET',
url,
data = null,
headers = {},
timeout = 0,
responseType = 'text',
onSuccess,
onError,
onTimeout,
onProgress
} = config;
// 创建XHR对象
const xhr = new XMLHttpRequest();
// 设置响应类型
xhr.responseType = responseType;
// 设置超时时间
xhr.timeout = timeout;
// 监听状态变化
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status >= 200 && xhr.status < 300) {
// 请求成功
if (onSuccess) {
onSuccess({
data: xhr.response,
status: xhr.status,
statusText: xhr.statusText,
headers: xhr.getAllResponseHeaders()
});
}
} else {
// 请求失败
if (onError) {
onError({
status: xhr.status,
statusText: xhr.statusText,
error: xhr.response
});
}
}
}
};
// 监听超时
xhr.ontimeout = function() {
if (onTimeout) onTimeout();
};
// 监听进度
if (onProgress) {
xhr.onprogress = onProgress;
if (xhr.upload && method !== 'GET') {
xhr.upload.onprogress = onProgress;
}
}
// 处理请求数据
let requestData = data;
let requestUrl = url;
// 对于GET请求,将数据附加到URL
if (method === 'GET' && data) {
const queryString = typeof data === 'object'
? Object.keys(data).map(key =>
`${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`
).join('&')
: data.toString();
if (queryString) {
requestUrl += (url.includes('?') ? '&' : '?') + queryString;
}
requestData = null;
} else if (data && typeof data === 'object') {
// 默认使用JSON格式
if (!headers['Content-Type']) {
headers['Content-Type'] = 'application/json;charset=UTF-8';
requestData = JSON.stringify(data);
}
}
// 打开连接
xhr.open(method, requestUrl, true);
// 设置请求头
Object.keys(headers).forEach(key => {
xhr.setRequestHeader(key, headers[key]);
});
// 发送请求
xhr.send(requestData);
return xhr;
}
二、实际应用示例
1. GET请求示例
// 获取用户信息
function getUser(userId) {
ajaxRequest({
method: 'GET',
url: `https://api.example.com/users/${userId}`,
onSuccess: function(response) {
console.log('用户数据:', response.data);
renderUserProfile(response.data);
},
onError: function(error) {
console.error('获取用户失败:', error);
showError('无法加载用户信息');
}
});
}
2. POST请求示例
// 提交表单数据
function submitForm(formData) {
ajaxRequest({
method: 'POST',
url: 'https://api.example.com/submit',
data: formData,
headers: {
'X-CSRF-Token': getCSRFToken() // 自定义请求头
},
onSuccess: function(response) {
console.log('提交成功:', response.data);
showSuccessMessage('操作成功完成');
},
onError: function(error) {
console.error('提交失败:', error);
if (error.status === 401) {
redirectToLogin();
} else {
showError('提交失败,请重试');
}
}
});
}
3. 上传文件
// 文件上传
function uploadFile(file, onProgressCallback) {
const formData = new FormData();
formData.append('file', file);
formData.append('description', '用户上传的文件');
ajaxRequest({
method: 'POST',
url: 'https://api.example.com/upload',
data: formData,
// FormData会自动设置Content-Type为multipart/form-data
onProgress: function(event) {
if (event.lengthComputable) {
const percentComplete = Math.round((event.loaded / event.total) * 100);
onProgressCallback(percentComplete);
}
},
onSuccess: function(response) {
console.log('上传成功:', response.data);
updateFileList(response.data.fileUrl);
},
onError: function(error) {
console.error('上传失败:', error);
showError('文件上传失败');
}
});
}
三、高级特性与最佳实践
1. 请求取消
class RequestManager {
constructor() {
this.pendingRequests = new Map();
}
// 发送请求并存储引用
sendRequest(requestId, config) {
const xhr = ajaxRequest({
...config,
onSuccess: (response) => {
this.pendingRequests.delete(requestId);
if (config.onSuccess) config.onSuccess(response);
},
onError: (error) => {
this.pendingRequests.delete(requestId);
if (config.onError) config.onError(error);
}
});
this.pendingRequests.set(requestId, xhr);
return requestId;
}
// 取消请求
cancelRequest(requestId) {
const xhr = this.pendingRequests.get(requestId);
if (xhr) {
xhr.abort();
this.pendingRequests.delete(requestId);
console.log(`请求 ${requestId} 已取消`);
}
}
// 取消所有请求
cancelAllRequests() {
this.pendingRequests.forEach(xhr => xhr.abort());
this.pendingRequests.clear();
}
}
2. 请求拦截器
class AjaxInterceptor {
constructor() {
this.requestInterceptors = [];
this.responseInterceptors = [];
}
// 添加请求拦截器
addRequestInterceptor(interceptor) {
this.requestInterceptors.push(interceptor);
}
// 添加响应拦截器
addResponseInterceptor(interceptor) {
this.responseInterceptors.push(interceptor);
}
// 发送请求(带拦截器)
request(config) {
// 执行请求拦截器
let processedConfig = { ...config };
this.requestInterceptors.forEach(interceptor => {
processedConfig = interceptor(processedConfig) || processedConfig;
});
// 发送请求
return new Promise((resolve, reject) => {
ajaxRequest({
...processedConfig,
onSuccess: (response) => {
// 执行响应拦截器
let processedResponse = response;
this.responseInterceptors.forEach(interceptor => {
processedResponse = interceptor(processedResponse) || processedResponse;
});
resolve(processedResponse);
},
onError: (error) => {
reject(error);
}
});
});
}
}
3. 自动重试机制
function ajaxWithRetry(config, maxRetries = 3, retryDelay = 1000) {
let retries = 0;
function attemptRequest() {
return new Promise((resolve, reject) => {
ajaxRequest({
...config,
onSuccess: resolve,
onError: (error) => {
retries++;
if (retries <= maxRetries && shouldRetry(error.status)) {
console.log(`请求失败,${retryDelay}ms后重试 (${retries}/${maxRetries})`);
setTimeout(() => {
attemptRequest().then(resolve).catch(reject);
}, retryDelay * retries); // 指数退避
} else {
reject(error);
}
}
});
});
}
function shouldRetry(status) {
// 对服务器错误和网络错误进行重试
return status >= 500 || status === 0 || status === 408;
}
return attemptRequest();
}
四、兼容性与注意事项
1. 跨域请求
// CORS处理
function corsRequest() {
ajaxRequest({
method: 'GET',
url: 'https://api.other-domain.com/data',
// 如果需要凭证
headers: {
// CORS相关头部由浏览器自动处理
},
// 注意:跨域请求默认不发送cookie
// 如果需要发送cookie,服务器需要设置Access-Control-Allow-Credentials: true
// 且前端需要设置xhr.withCredentials = true
beforeSend: (xhr) => {
xhr.withCredentials = true;
}
});
}
2. 错误处理最佳实践
// 统一错误处理
function safeAjaxRequest(config) {
return new Promise((resolve, reject) => {
ajaxRequest({
...config,
onSuccess: (response) => {
// 检查业务逻辑错误
if (response.data && response.data.code !== 0) {
handleBusinessError(response.data);
reject(new Error(response.data.message));
} else {
resolve(response);
}
},
onError: (error) => {
// 统一错误处理
const errorMessage = getErrorMessage(error.status);
showNotification(errorMessage, 'error');
logError(error);
reject(error);
}
});
});
}
function getErrorMessage(status) {
const errorMessages = {
400: '请求参数错误',
401: '请先登录',
403: '没有权限',
404: '资源不存在',
500: '服务器错误',
502: '网关错误',
503: '服务不可用',
504: '网关超时',
0: '网络连接失败'
};
return errorMessages[status] || `请求失败 (${status})`;
}
五、现代化替代方案
虽然XMLHttpRequest是Ajax的原始实现,但现在更推荐使用Fetch API:
// Fetch API 示例
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('请求失败:', error);
throw error;
}
}
总结
Ajax通过XMLHttpRequest对象实现了异步通信,虽然现在有更现代的Fetch API,但理解XMLHttpRequest的工作原理仍然很重要,因为:
兼容性:XMLHttpRequest在几乎所有浏览器中都得到支持
控制力:提供更细粒度的控制(如进度监控)
历史代码:许多遗留系统仍在使用
建议在新项目中使用Fetch API或基于Promise的HTTP客户端(如Axios),但在需要更精细控制或兼容旧浏览器时,XMLHttpRequest仍然是可靠的选择。