AJAX(Asynchronous JavaScript and XML)的封装可以提升代码复用性和可维护性。以下是几种常见的封装方法:
1. 原生XMLHttpRequest基础封装
function ajax(options) {
const defaultOptions = {
url: '',
method: 'GET',
data: null,
headers: {},
timeout: 10000,
success: () => {},
error: () => {}
};
const opts = Object.assign({}, defaultOptions, options);
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
// 处理查询参数(GET请求)
let url = opts.url;
if (opts.method.toUpperCase() === 'GET' && opts.data) {
const params = new URLSearchParams(opts.data).toString();
url += (url.includes('?') ? '&' : '?') + params;
}
xhr.open(opts.method, url, true);
// 设置请求头
Object.keys(opts.headers).forEach(key => {
xhr.setRequestHeader(key, opts.headers[key]);
});
// 设置超时
xhr.timeout = opts.timeout;
// 监听状态变化
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
try {
const response = xhr.responseText;
const result = opts.dataType === 'json'
? JSON.parse(response)
: response;
opts.success(result);
resolve(result);
} catch (e) {
opts.error(e);
reject(e);
}
} else {
const error = new Error(`HTTP ${xhr.status}`);
opts.error(error);
reject(error);
}
}
};
// 错误处理
xhr.onerror = function() {
const error = new Error('Network Error');
opts.error(error);
reject(error);
};
xhr.ontimeout = function() {
const error = new Error('Request Timeout');
opts.error(error);
reject(error);
};
// 发送请求
const sendData = opts.method.toUpperCase() === 'POST'
? (typeof opts.data === 'object'
? JSON.stringify(opts.data)
: opts.data)
: null;
xhr.send(sendData);
});
}
// 使用示例
ajax({
url: '/api/users',
method: 'GET',
data: { page: 1, limit: 10 },
dataType: 'json',
success: (data) => console.log('Success:', data),
error: (err) => console.error('Error:', err)
});
2. Fetch API封装
class HttpRequest {
constructor(baseURL = '', defaultHeaders = {}) {
this.baseURL = baseURL;
this.defaultHeaders = {
'Content-Type': 'application/json',
...defaultHeaders
};
}
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
const headers = { ...this.defaultHeaders, ...options.headers };
const config = {
...options,
headers
};
try {
const response = await fetch(url, config);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const contentType = response.headers.get('content-type');
let data;
if (contentType && contentType.includes('application/json')) {
data = await response.json();
} else {
data = await response.text();
}
return {
ok: true,
status: response.status,
data,
headers: response.headers
};
} catch (error) {
return {
ok: false,
error: error.message,
status: error.status || 0
};
}
}
get(endpoint, params = {}, options = {}) {
let url = endpoint;
if (Object.keys(params).length > 0) {
const queryString = new URLSearchParams(params).toString();
url += `?${queryString}`;
}
return this.request(url, { ...options, method: 'GET' });
}
post(endpoint, data = {}, options = {}) {
return this.request(endpoint, {
...options,
method: 'POST',
body: JSON.stringify(data)
});
}
put(endpoint, data = {}, options = {}) {
return this.request(endpoint, {
...options,
method: 'PUT',
body: JSON.stringify(data)
});
}
delete(endpoint, options = {}) {
return this.request(endpoint, { ...options, method: 'DELETE' });
}
// 上传文件
upload(endpoint, formData, options = {}) {
const headers = { ...options.headers };
delete headers['Content-Type']; // 让浏览器自动设置multipart/form-data
return this.request(endpoint, {
...options,
method: 'POST',
headers,
body: formData
});
}
}
// 使用示例
const api = new HttpRequest('https://api.example.com');
// GET请求
api.get('/users', { page: 1 })
.then(result => {
if (result.ok) {
console.log(result.data);
}
});
// POST请求
api.post('/users', { name: 'John', age: 25 })
.then(result => console.log(result));
3. 基于Promise的完整封装
class AjaxService {
constructor(config = {}) {
this.config = {
baseURL: '',
timeout: 10000,
headers: {
'Content-Type': 'application/json'
},
withCredentials: false,
...config
};
// 请求拦截器
this.interceptors = {
request: [],
response: []
};
}
// 添加拦截器
use(interceptor) {
if (interceptor.request) {
this.interceptors.request.push(interceptor.request);
}
if (interceptor.response) {
this.interceptors.response.push(interceptor.response);
}
}
// 执行拦截器链
async runInterceptors(interceptorType, value) {
let result = value;
const interceptors = this.interceptors[interceptorType];
for (const interceptor of interceptors) {
result = await interceptor(result);
}
return result;
}
async request(method, url, data = null, options = {}) {
const config = {
method: method.toUpperCase(),
url: this.config.baseURL + url,
data,
headers: { ...this.config.headers, ...options.headers },
timeout: options.timeout || this.config.timeout,
withCredentials: options.withCredentials || this.config.withCredentials
};
try {
// 请求拦截
const requestConfig = await this.runInterceptors('request', config);
// 创建请求
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), requestConfig.timeout);
const fetchOptions = {
method: requestConfig.method,
headers: requestConfig.headers,
signal: controller.signal,
credentials: requestConfig.withCredentials ? 'include' : 'same-origin'
};
if (requestConfig.data && requestConfig.method !== 'GET') {
fetchOptions.body = typeof requestConfig.data === 'object'
? JSON.stringify(requestConfig.data)
: requestConfig.data;
} else if (requestConfig.method === 'GET' && requestConfig.data) {
const params = new URLSearchParams(requestConfig.data);
requestConfig.url += `?${params.toString()}`;
}
const response = await fetch(requestConfig.url, fetchOptions);
clearTimeout(timeoutId);
// 处理响应
let responseData;
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
responseData = await response.json();
} else {
responseData = await response.text();
}
const responseObj = {
data: responseData,
status: response.status,
statusText: response.statusText,
headers: response.headers,
config: requestConfig
};
// 响应拦截
return await this.runInterceptors('response', responseObj);
} catch (error) {
// 错误处理
const errorObj = {
message: error.name === 'AbortError' ? '请求超时' : error.message,
config,
error
};
throw errorObj;
}
}
// 快捷方法
get(url, params = {}, options = {}) {
return this.request('GET', url, params, options);
}
post(url, data = {}, options = {}) {
return this.request('POST', url, data, options);
}
put(url, data = {}, options = {}) {
return this.request('PUT', url, data, options);
}
patch(url, data = {}, options = {}) {
return this.request('PATCH', url, data, options);
}
delete(url, options = {}) {
return this.request('DELETE', url, null, options);
}
}
// 使用示例
const service = new AjaxService({
baseURL: 'https://api.example.com'
});
// 添加拦截器
service.use({
request: (config) => {
// 添加认证token
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
response: (response) => {
// 统一处理响应
if (response.status === 401) {
// 处理未授权
window.location.href = '/login';
}
return response;
}
});
// 发送请求
service.get('/users')
.then(response => console.log(response.data))
.catch(error => console.error(error));
4. 并发请求处理
class RequestManager {
constructor(maxConcurrent = 5) {
this.maxConcurrent = maxConcurrent;
this.queue = [];
this.activeCount = 0;
this.requestMap = new Map();
}
async add(requestFn, id = Symbol()) {
return new Promise((resolve, reject) => {
this.queue.push({
requestFn,
resolve,
reject,
id
});
this.processQueue();
});
}
async processQueue() {
if (this.activeCount >= this.maxConcurrent || this.queue.length === 0) {
return;
}
this.activeCount++;
const { requestFn, resolve, reject, id } = this.queue.shift();
try {
const result = await requestFn();
resolve(result);
} catch (error) {
reject(error);
} finally {
this.activeCount--;
this.processQueue();
}
}
// 批量请求
async all(requests) {
return Promise.all(requests.map(req => this.add(req.fn, req.id)));
}
// 取消请求
cancel(id) {
const index = this.queue.findIndex(item => item.id === id);
if (index > -1) {
this.queue.splice(index, 1);
}
}
}
// 使用示例
const manager = new RequestManager(3);
// 添加多个请求
const requests = [
{ fn: () => fetch('/api/users'), id: 'users' },
{ fn: () => fetch('/api/posts'), id: 'posts' },
{ fn: () => fetch('/api/comments'), id: 'comments' }
];
manager.all(requests)
.then(results => console.log(results))
.catch(error => console.error(error));
5. 简化的工厂函数封装
function createApiClient(config = {}) {
const {
baseURL = '',
headers = {},
transformRequest = data => data,
transformResponse = response => response
} = config;
const client = {
async request(method, url, data, options = {}) {
const finalUrl = baseURL + url;
const finalHeaders = { ...headers, ...options.headers };
// 转换请求数据
const transformedData = transformRequest(data);
const response = await fetch(finalUrl, {
method,
headers: finalHeaders,
body: method !== 'GET' ? JSON.stringify(transformedData) : undefined
});
const result = await response.json();
return transformResponse(result);
}
};
// 创建快捷方法
['get', 'post', 'put', 'delete', 'patch'].forEach(method => {
client[method] = (url, data, options) =>
client.request(method.toUpperCase(), url, data, options);
});
return client;
}
// 使用示例
const api = createApiClient({
baseURL: '/api',
transformRequest: (data) => {
// 添加时间戳
return { ...data, timestamp: Date.now() };
},
transformResponse: (response) => {
// 统一处理响应格式
return response.data || response;
}
});
// 简洁的调用方式
api.get('/users').then(console.log);
api.post('/users', { name: 'John' }).then(console.log);
总结
| 封装方式 |
优点 |
适用场景 |
|---|
| 原生封装 |
无依赖,兼容性好 |
简单项目或需要最小化依赖 |
| Fetch封装 |
现代API,Promise原生支持 |
现代浏览器项目 |
| 完整类封装 |
功能全面,可扩展性强 |
大型项目,需要拦截器等功能 |
| 并发管理 |
控制并发数,避免过多请求 |
批量请求场景 |
| 工厂函数 |
简洁灵活,配置化 |
需要快速创建多个不同配置的客户端 |
选择哪种封装方式取决于项目需求、团队习惯和技术栈。对于现代项目,推荐使用Fetch API或基于类的完整封装方案。