Ajax(Asynchronous JavaScript and XML)是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。通过异步方式与服务器交换数据,实现页面的局部刷新。
// 原生 Ajax 示例
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.log(JSON.parse(xhr.responseText));
} else {
console.error('请求失败');
}
}
};
xhr.send();
原生 Ajax 存在以下问题:
/**
* 基础 Ajax 封装
* @param {Object} options 配置选项
*/
function ajax(options = {}) {
const {
url,
method = 'GET',
data = null,
headers = {},
timeout = 10000,
success,
error
} = options;
const xhr = new XMLHttpRequest();
// 处理 GET 请求的查询参数
let requestUrl = url;
if (method === 'GET' && data) {
const params = new URLSearchParams(data).toString();
requestUrl = `${url}?${params}`;
}
xhr.open(method, requestUrl, true);
// 设置请求头
xhr.setRequestHeader('Content-Type', 'application/json');
Object.keys(headers).forEach(key => {
xhr.setRequestHeader(key, headers[key]);
});
// 超时处理
xhr.timeout = timeout;
// 响应处理
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
const response = {
data: parseResponse(xhr.responseText),
status: xhr.status,
statusText: xhr.statusText
};
success && success(response);
} else {
error && error(new Error(`请求失败: ${xhr.status}`));
}
}
};
xhr.ontimeout = function() {
error && error(new Error('请求超时'));
};
xhr.onerror = function() {
error && error(new Error('网络错误'));
};
// 发送请求
xhr.send(method !== 'GET' ? JSON.stringify(data) : null);
// 解析响应
function parseResponse(response) {
try {
return JSON.parse(response);
} catch {
return response;
}
}
}
/**
* Promise 风格的 Ajax 封装
*/
function ajaxPromise(options = {}) {
return new Promise((resolve, reject) => {
const {
url,
method = 'GET',
data = null,
headers = {},
timeout = 10000
} = options;
const xhr = new XMLHttpRequest();
// 处理 GET 请求参数
let requestUrl = url;
if (method === 'GET' && data) {
const params = new URLSearchParams(data).toString();
requestUrl = `${url}?${params}`;
}
xhr.open(method, requestUrl, true);
// 设置请求头
Object.entries({
'Content-Type': 'application/json',
...headers
}).forEach(([key, value]) => {
xhr.setRequestHeader(key, value);
});
xhr.timeout = timeout;
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
try {
const response = xhr.responseText
? JSON.parse(xhr.responseText)
: xhr.responseText;
resolve({
data: response,
status: xhr.status,
statusText: xhr.statusText
});
} catch (error) {
resolve({
data: xhr.responseText,
status: xhr.status,
statusText: xhr.statusText
});
}
} else {
reject(new Error(`请求失败: ${xhr.status}`));
}
};
xhr.ontimeout = function() {
reject(new Error('请求超时'));
};
xhr.onerror = function() {
reject(new Error('网络错误'));
};
// 发送请求
const requestData = method !== 'GET' && data
? JSON.stringify(data)
: null;
xhr.send(requestData);
});
}
class Ajax {
constructor(baseURL = '') {
this.baseURL = baseURL;
this.interceptors = {
request: [],
response: []
};
this.pendingRequests = new Map();
}
/**
* 添加请求拦截器
*/
addRequestInterceptor(interceptor) {
this.interceptors.request.push(interceptor);
}
/**
* 添加响应拦截器
*/
addResponseInterceptor(interceptor) {
this.interceptors.response.push(interceptor);
}
/**
* 执行请求
*/
request(config) {
// 生成请求唯一标识
const requestId = `${config.method}-${config.url}-${Date.now()}`;
// 执行请求拦截器
let processedConfig = { ...config };
this.interceptors.request.forEach(interceptor => {
processedConfig = interceptor(processedConfig);
});
// 创建取消令牌
const controller = new AbortController();
this.pendingRequests.set(requestId, controller);
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
const url = this.baseURL + processedConfig.url;
xhr.open(
processedConfig.method || 'GET',
url,
true
);
// 设置请求头
const headers = {
'Content-Type': 'application/json',
...processedConfig.headers
};
Object.entries(headers).forEach(([key, value]) => {
xhr.setRequestHeader(key, value);
});
// 超时设置
xhr.timeout = processedConfig.timeout || 10000;
// 设置中止信号
if (controller.signal) {
controller.signal.onabort = () => {
xhr.abort();
reject(new Error('请求已被取消'));
};
}
xhr.onload = () => {
this.pendingRequests.delete(requestId);
let response = {
data: this.parseResponse(xhr.responseText),
status: xhr.status,
statusText: xhr.statusText,
headers: this.parseHeaders(xhr.getAllResponseHeaders()),
config: processedConfig
};
// 执行响应拦截器
this.interceptors.response.forEach(interceptor => {
response = interceptor(response);
});
if (xhr.status >= 200 && xhr.status < 300) {
resolve(response);
} else {
reject(this.createError('请求失败', processedConfig, xhr, response));
}
};
xhr.ontimeout = () => {
this.pendingRequests.delete(requestId);
reject(this.createError('请求超时', processedConfig, xhr));
};
xhr.onerror = () => {
this.pendingRequests.delete(requestId);
reject(this.createError('网络错误', processedConfig, xhr));
};
// 发送数据
const data = processedConfig.method !== 'GET' && processedConfig.data
? JSON.stringify(processedConfig.data)
: null;
xhr.send(data);
});
}
/**
* 取消请求
*/
cancelRequest(requestId) {
const controller = this.pendingRequests.get(requestId);
if (controller) {
controller.abort();
this.pendingRequests.delete(requestId);
}
}
/**
* 取消所有请求
*/
cancelAllRequests() {
this.pendingRequests.forEach(controller => controller.abort());
this.pendingRequests.clear();
}
/**
* 解析响应
*/
parseResponse(response) {
try {
return JSON.parse(response);
} catch {
return response;
}
}
/**
* 解析响应头
*/
parseHeaders(headersString) {
const headers = {};
if (headersString) {
headersString.split('\r\n').forEach(line => {
const parts = line.split(': ');
if (parts.length === 2) {
headers[parts[0]] = parts[1];
}
});
}
return headers;
}
/**
* 创建错误对象
*/
createError(message, config, xhr, response) {
const error = new Error(message);
error.config = config;
error.code = xhr.status;
error.request = xhr;
error.response = response;
return error;
}
/**
* 快捷方法
*/
get(url, config = {}) {
return this.request({ ...config, method: 'GET', url });
}
post(url, data = {}, config = {}) {
return this.request({ ...config, method: 'POST', url, data });
}
put(url, data = {}, config = {}) {
return this.request({ ...config, method: 'PUT', url, data });
}
delete(url, config = {}) {
return this.request({ ...config, method: 'DELETE', url });
}
}
// 创建实例
const http = new Ajax('https://api.example.com');
// 添加拦截器
http.addRequestInterceptor(config => {
// 添加认证 token
config.headers.Authorization = `Bearer ${localStorage.getItem('token')}`;
return config;
});
http.addResponseInterceptor(response => {
// 统一处理响应
if (response.status === 401) {
// 跳转到登录页
window.location.href = '/login';
}
return response;
});
// 发送请求
http.get('/users')
.then(response => {
console.log('数据:', response.data);
})
.catch(error => {
console.error('错误:', error);
});
// POST 请求
http.post('/users', {
name: '张三',
age: 25
})
.then(response => {
console.log('创建成功');
});
const requestId = 'user-list-request';
// 发送请求
http.request({
url: '/users',
method: 'GET',
requestId: requestId
})
.then(response => {
console.log(response.data);
})
.catch(error => {
if (error.message === '请求已被取消') {
console.log('请求已取消');
}
});
// 在需要时取消请求
setTimeout(() => {
http.cancelRequest(requestId);
}, 1000);
// jQuery
$.ajax({
url: '/api/data',
method: 'GET',
success: function(data) {
console.log(data);
}
});
// 自定义封装
ajax({
url: '/api/data',
method: 'GET',
success: function(response) {
console.log(response.data);
}
});
// axios
axios.get('/api/data')
.then(response => {
console.log(response.data);
});
// 自定义封装
http.get('/api/data')
.then(response => {
console.log(response.data);
});
interface AjaxConfig {
url: string;
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
data?: any;
headers?: Record<string, string>;
timeout?: number;
}
interface AjaxResponse<T = any> {
data: T;
status: number;
statusText: string;
headers: Record<string, string>;
config: AjaxConfig;
}
class Ajax {
// ... TypeScript 实现
}
封装 Ajax 可以显著提升代码的可维护性和开发效率。根据项目需求,可以选择不同的封装策略。对于简单项目,基础封装即可满足需求;对于复杂的企业级应用,建议使用更完整的封装方案,包含拦截器、取消请求、错误处理等高级功能。
在实际开发中,也可以考虑使用成熟的第三方库如 axios,它们经过了充分测试,功能完善,社区支持好。但如果需要特殊定制或有特殊需求,自己封装也是一个不错的选择。