// 原生XMLHttpRequest实现
function ajaxRequest(options) {
// 默认参数
const defaultOptions = {
url: '',
method: 'GET',
data: null,
headers: {
'Content-Type': 'application/json'
},
async: true,
timeout: 10000,
success: null,
error: null
};
// 合并选项
options = Object.assign({}, defaultOptions, options);
// 创建xhr对象
const xhr = new XMLHttpRequest();
// 设置超时
xhr.timeout = options.timeout;
// 监听状态变化
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
let response;
try {
response = JSON.parse(xhr.responseText);
} catch (e) {
response = xhr.responseText;
}
options.success && options.success(response);
} else {
options.error && options.error(xhr.status, xhr.statusText);
}
}
};
// 错误处理
xhr.onerror = function() {
options.error && options.error(xhr.status, xhr.statusText);
};
// 超时处理
xhr.ontimeout = function() {
options.error && options.error('timeout', '请求超时');
};
// 处理GET请求参数
if (options.method.toUpperCase() === 'GET' && options.data) {
const queryString = typeof options.data === 'object'
? Object.keys(options.data).map(key => `${encodeURIComponent(key)}=${encodeURIComponent(options.data[key])}`).join('&')
: options.data;
options.url += (options.url.includes('?') ? '&' : '?') + queryString;
options.data = null;
}
// 打开连接
xhr.open(options.method, options.url, options.async);
// 设置请求头
Object.keys(options.headers).forEach(key => {
xhr.setRequestHeader(key, options.headers[key]);
});
// 发送请求
if (options.method.toUpperCase() === 'POST' || options.method.toUpperCase() === 'PUT') {
const data = options.headers['Content-Type'].includes('application/json')
? JSON.stringify(options.data)
: options.data;
xhr.send(data);
} else {
xhr.send();
}
// 返回xhr对象,方便外部控制
return xhr;
}
// GET请求示例
ajaxRequest({
url: 'https://api.example.com/users',
method: 'GET',
success: function(data) {
console.log('获取用户列表成功:', data);
},
error: function(status, message) {
console.error('获取失败:', status, message);
}
});
// POST请求示例
ajaxRequest({
url: 'https://api.example.com/users',
method: 'POST',
data: {
name: '张三',
age: 30,
email: '[email protected]'
},
success: function(response) {
console.log('创建用户成功:', response);
},
error: function(status, message) {
console.error('创建失败:', status, message);
}
});
// 基于Promise的Ajax封装
function ajax(options) {
return new Promise((resolve, reject) => {
// 默认配置
const defaultOptions = {
url: '',
method: 'GET',
data: null,
headers: {
'Content-Type': 'application/json'
},
timeout: 10000,
withCredentials: false // 是否携带凭证
};
// 合并配置
options = {...defaultOptions, ...options};
// 创建xhr对象
const xhr = new XMLHttpRequest();
// 设置超时
xhr.timeout = options.timeout;
// 设置是否携带凭证
xhr.withCredentials = options.withCredentials;
// 监听加载完成
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
let response;
try {
response = JSON.parse(xhr.responseText);
} catch (e) {
response = xhr.responseText;
}
resolve(response);
} else {
reject({
status: xhr.status,
statusText: xhr.statusText,
response: xhr.responseText
});
}
};
// 监听错误
xhr.onerror = function() {
reject({
status: xhr.status,
statusText: '网络错误',
response: null
});
};
// 监听超时
xhr.ontimeout = function() {
reject({
status: 'timeout',
statusText: '请求超时',
response: null
});
};
// 处理GET请求参数
if (options.method.toUpperCase() === 'GET' && options.data) {
const queryString = typeof options.data === 'object'
? Object.keys(options.data).map(key => `${encodeURIComponent(key)}=${encodeURIComponent(options.data[key])}`).join('&')
: options.data;
options.url += (options.url.includes('?') ? '&' : '?') + queryString;
options.data = null;
}
// 打开连接
xhr.open(options.method, options.url, true);
// 设置请求头
Object.keys(options.headers).forEach(key => {
xhr.setRequestHeader(key, options.headers[key]);
});
// 发送请求
if (options.method.toUpperCase() === 'POST' || options.method.toUpperCase() === 'PUT') {
const contentType = options.headers['Content-Type'];
let data = options.data;
if (contentType && contentType.includes('application/json') && typeof data === 'object') {
data = JSON.stringify(data);
} else if (contentType && contentType.includes('application/x-www-form-urlencoded') && typeof data === 'object') {
data = Object.keys(data).map(key => `${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`).join('&');
}
xhr.send(data);
} else {
xhr.send();
}
});
}
在实际工作中,我通常会封装一个更完善的HTTP请求库,包含以下特性:
// 拦截器实现
const httpClient = {
interceptors: {
request: [],
response: []
},
// 添加请求拦截器
addRequestInterceptor(fulfilled, rejected) {
this.interceptors.request.push({ fulfilled, rejected });
},
// 添加响应拦截器
addResponseInterceptor(fulfilled, rejected) {
this.interceptors.response.push({ fulfilled, rejected });
},
// 执行请求
request(config) {
// 应用请求拦截器
let requestConfig = {...config};
this.interceptors.request.forEach(interceptor => {
if (interceptor.fulfilled) {
requestConfig = interceptor.fulfilled(requestConfig);
}
});
// 发起请求
return ajax(requestConfig).then(
response => {
// 应用响应拦截器
let responseResult = response;
this.interceptors.response.forEach(interceptor => {
if (interceptor.fulfilled) {
responseResult = interceptor.fulfilled(responseResult);
}
});
return responseResult;
},
error => {
// 应用错误拦截器
let errorResult = error;
this.interceptors.response.forEach(interceptor => {
if (interceptor.rejected) {
errorResult = interceptor.rejected(errorResult);
}
});
return Promise.reject(errorResult);
}
);
},
// 便捷方法
get(url, params, config = {}) {
return this.request({
...config,
method: 'GET',
url,
data: params
});
},
post(url, data, config = {}) {
return this.request({
...config,
method: 'POST',
url,
data
});
}
// 其他方法...
};
// 错误处理示例
httpClient.addResponseInterceptor(
response => response,
error => {
if (error.status === 401) {
// 未授权,跳转登录
location.href = '/login';
} else if (error.status === 403) {
// 权限不足
showNotification('您没有权限执行此操作');
} else if (error.status === 500) {
// 服务器错误
showNotification('服务器错误,请稍后再试');
} else if (error.status === 'timeout') {
// 超时处理
showNotification('请求超时,请检查网络');
}
return Promise.reject(error);
}
);
// 请求取消功能
function createCancelToken() {
let cancel;
const token = new Promise((resolve) => {
cancel = resolve;
});
token.cancel = cancel;
return token;
}
// 使用方式
const cancelToken = createCancelToken();
httpClient.get('/api/data', null, { cancelToken })
.catch(error => {
if (error.isCanceled) {
console.log('请求已取消');
}
});
// 取消请求
cancelToken.cancel('用户取消');
// 简单请求缓存实现
const cacheMap = new Map();
httpClient.addRequestInterceptor(config => {
// 只对GET请求进行缓存
if (config.method === 'GET' && config.useCache) {
const cacheKey = `${config.url}${JSON.stringify(config.data || {})}`;
const cachedResponse = cacheMap.get(cacheKey);
if (cachedResponse && Date.now() - cachedResponse.timestamp < config.cacheTime) {
// 使用缓存数据
config.useCache = cachedResponse.data;
} else {
// 标记需要缓存
config._cacheKey = cacheKey;
}
}
return config;
});
httpClient.addResponseInterceptor(response => {
const config = response.config;
// 如果请求需要缓存
if (config.method === 'GET' && config.useCache && config._cacheKey) {
cacheMap.set(config._cacheKey, {
data: response,
timestamp: Date.now()
});
}
return response;
});
// 请求重试功能
function requestWithRetry(config, maxRetries = 3, delay = 1000) {
return httpClient.request(config).catch(error => {
// 只在网络错误或服务器错误时重试
if ((error.status === 0 || error.status >= 500) && maxRetries > 0) {
return new Promise(resolve => {
setTimeout(() => {
resolve(requestWithRetry(config, maxRetries - 1, delay * 2));
}, delay);
});
}
return Promise.reject(error);
});
}
在实际工作中,通常不会完全从零开始封装Ajax,而是基于成熟的库如Axios进行二次封装,增加项目所需的特定功能。
通过封装Ajax,能够: