引言
在现代Web开发中,网络请求是实现前后端数据交互的核心技术。从基础的XMLHttpRequest到现代的Fetch API,再到功能丰富的WebSocket,浏览器提供了多种网络通信机制。本文将全面深入探讨浏览器网络请求相关的API,提供高级封装方案,并涵盖实际开发中的各种复杂场景。
一、现代网络请求API概览
1.1 从XMLHttpRequest到Fetch API
javascript
class NetworkAPIEvolution {
// 传统的XMLHttpRequest实现
static xhrRequest(url, options = {}) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
const {
method = 'GET',
headers = {},
data = null,
timeout = 0,
responseType = ''
} = options;
xhr.open(method, url, true);
// 设置请求头
Object.entries(headers).forEach(([key, value]) => {
xhr.setRequestHeader(key, value);
});
// 设置响应类型
if (responseType) {
xhr.responseType = responseType;
}
// 设置超时
xhr.timeout = timeout;
// 事件处理
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
let response;
try {
if (xhr.responseType === 'json') {
response = xhr.response;
} else if (xhr.responseType === 'blob') {
response = xhr.response;
} else {
response = JSON.parse(xhr.responseText);
}
} catch (e) {
response = xhr.responseText;
}
resolve({
data: response,
status: xhr.status,
statusText: xhr.statusText,
headers: xhr.getAllResponseHeaders()
});
} else {
reject(new Error(`HTTP ${xhr.status}: ${xhr.statusText}`));
}
};
xhr.onerror = function() {
reject(new Error('Network error'));
};
xhr.ontimeout = function() {
reject(new Error('Request timeout'));
};
xhr.onprogress = function(event) {
if (event.lengthComputable) {
const percentComplete = (event.loaded / event.total) * 100;
console.log(`Upload progress: ${percentComplete.toFixed(2)}%`);
}
};
xhr.send(data);
});
}
// Fetch API基础实现
static async fetchRequest(url, options = {}) {
const {
method = 'GET',
headers = {},
body = null,
timeout = 30000,
credentials = 'same-origin',
mode = 'cors',
cache = 'default',
redirect = 'follow',
referrer = 'client'
} = options;
// 创建AbortController用于超时控制
const controller = new AbortController();
const signal = controller.signal;
// 设置超时
const timeoutId = setTimeout(() => {
controller.abort();
}, timeout);
try {
const response = await fetch(url, {
method,
headers,
body,
credentials,
mode,
cache,
redirect,
referrer,
signal
});
clearTimeout(timeoutId);
// 检查响应状态
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
// 根据Content-Type解析响应
const contentType = response.headers.get('content-type');
let data;
if (contentType && contentType.includes('application/json')) {
data = await response.json();
} else if (contentType && contentType.includes('text/')) {
data = await response.text();
} else if (contentType && contentType.includes('multipart/form-data')) {
data = await response.formData();
} else {
data = await response.blob();
}
return {
data,
status: response.status,
statusText: response.statusText,
headers: response.headers,
response
};
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('Request timeout');
}
throw error;
}
}
// 性能对比
static async performanceComparison() {
const testUrl = 'https://jsonplaceholder.typicode.com/posts/1';
const iterations = 10;
const results = {
xhr: { times: [], avg: 0 },
fetch: { times: [], avg: 0 }
};
console.log('Starting performance comparison...');
// 测试XHR
for (let i = 0; i < iterations; i++) {
const start = performance.now();
await this.xhrRequest(testUrl);
const end = performance.now();
results.xhr.times.push(end - start);
}
// 测试Fetch
for (let i = 0; i < iterations; i++) {
const start = performance.now();
await this.fetchRequest(testUrl);
const end = performance.now();
results.fetch.times.push(end - start);
}
// 计算平均值
results.xhr.avg = results.xhr.times.reduce((a, b) => a + b, 0) / iterations;
results.fetch.avg = results.fetch.times.reduce((a, b) => a + b, 0) / iterations;
console.log('Performance results:', results);
console.log(`XHR average: ${results.xhr.avg.toFixed(2)}ms`);
console.log(`Fetch average: ${results.fetch.avg.toFixed(2)}ms`);
return results;
}
}
1.2 Fetch API的局限性及解决方案
javascript
class FetchLimitations {
// 处理Fetch不支持的特性
static getFetchPolyfills() {
const polyfills = {};
// 1. 进度监控
if (!('upload' in new Request(''))) {
polyfills.progress = async function(url, options = {}) {
const { onProgress, ...fetchOptions } = options;
// 使用XHR来获取进度
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(options.method || 'GET', url);
// 设置请求头
if (options.headers) {
Object.entries(options.headers).forEach(([key, value]) => {
xhr.setRequestHeader(key, value);
});
}
// 进度事件
if (onProgress) {
xhr.upload.onprogress = onProgress;
xhr.onprogress = onProgress;
}
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
let data;
try {
data = JSON.parse(xhr.responseText);
} catch {
data = xhr.responseText;
}
resolve({
data,
status: xhr.status,
statusText: xhr.statusText
});
} else {
reject(new Error(`HTTP ${xhr.status}`));
}
};
xhr.onerror = reject;
xhr.ontimeout = reject;
xhr.send(options.body);
});
};
}
// 2. 超时处理
if (!('timeout' in new Request(''))) {
polyfills.timeout = function(url, options = {}) {
const { timeout = 30000, ...fetchOptions } = options;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
return fetch(url, { ...fetchOptions, signal: controller.signal })
.finally(() => clearTimeout(timeoutId));
};
}
// 3. 请求取消
polyfills.cancelable = function(url, options = {}) {
const controller = new AbortController();
const promise = fetch(url, { ...options, signal: controller.signal });
return {
promise,
cancel: () => controller.abort()
};
};
return polyfills;
}
// 处理流式响应
static async handleStreamResponse(url, onChunk, onComplete) {
try {
const response = await fetch(url);
const reader = response.body.getReader();
const decoder = new TextDecoder();
let result = '';
while (true) {
const { done, value } = await reader.read();
if (done) {
if (onComplete) onComplete(result);
break;
}
// 处理chunk
const chunk = decoder.decode(value, { stream: true });
result += chunk;
if (onChunk) onChunk(chunk, result);
}
return result;
} catch (error) {
throw error;
}
}
// 处理大文件上传
static async uploadLargeFile(url, file, options = {}) {
const {
chunkSize = 1024 * 1024, // 1MB chunks
onProgress,
onComplete,
onError
} = options;
// 创建FormData
const formData = new FormData();
formData.append('file', file);
formData.append('fileName', file.name);
formData.append('fileSize', file.size);
formData.append('chunkSize', chunkSize);
formData.append('totalChunks', Math.ceil(file.size / chunkSize));
// 如果文件很大,使用分片上传
if (file.size > chunkSize) {
return this.uploadInChunks(url, file, {
chunkSize,
onProgress,
onComplete,
onError
});
}
// 普通上传
return fetch(url, {
method: 'POST',
body: formData
});
}
static async uploadInChunks(url, file, options) {
const {
chunkSize = 1024 * 1024,
onProgress,
onComplete,
onError
} = options;
const totalChunks = Math.ceil(file.size / chunkSize);
const fileId = `${Date.now()}-${Math.random().toString(36).substr(2)}`;
let uploadedChunks = 0;
// 上传每个分片
for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
const start = chunkIndex * chunkSize;
const end = Math.min(start + chunkSize, file.size);
const chunk = file.slice(start, end);
const formData = new FormData();
formData.append('fileId', fileId);
formData.append('chunk', chunk);
formData.append('chunkIndex', chunkIndex);
formData.append('totalChunks', totalChunks);
formData.append('fileName', file.name);
formData.append('fileSize', file.size);
try {
const response = await fetch(`${url}/chunk`, {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error(`Chunk ${chunkIndex} upload failed`);
}
uploadedChunks++;
// 报告进度
if (onProgress) {
const progress = (uploadedChunks / totalChunks) * 100;
onProgress(progress, chunkIndex, totalChunks);
}
} catch (error) {
if (onError) onError(error, chunkIndex);
throw error;
}
}
// 所有分片上传完成,合并文件
try {
const response = await fetch(`${url}/merge`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
fileId,
fileName: file.name,
totalChunks
})
});
if (onComplete) onComplete(response);
return response;
} catch (error) {
if (onError) onError(error);
throw error;
}
}
}
二、增强型Fetch封装
2.1 完整的HTTP客户端实现
javascript
class HttpClient {
constructor(baseURL = '', defaultOptions = {}) {
this.baseURL = baseURL;
this.defaultOptions = {
headers: {
'Content-Type': 'application/json',
...defaultOptions.headers
},
timeout: 30000,
credentials: 'include',
mode: 'cors',
...defaultOptions
};
this.interceptors = {
request: [],
response: [],
error: []
};
this.pendingRequests = new Map();
this.cache = new Map();
this.retryConfig = {
maxRetries: 3,
retryDelay: 1000,
retryOn: [408, 429, 500, 502, 503, 504]
};
// 初始化请求队列
this.requestQueue = [];
this.maxConcurrent = 6;
this.activeRequests = 0;
this.init();
}
init() {
// 添加默认拦截器
this.addDefaultInterceptors();
}
addDefaultInterceptors() {
// 请求拦截器:添加认证token
this.interceptors.request.use(
async (config) => {
// 从localStorage或cookie获取token
const token = localStorage.getItem('auth_token') ||
this.getCookie('auth_token');
if (token) {
config.headers = {
...config.headers,
'Authorization': `Bearer ${token}`
};
}
// 添加请求时间戳防止缓存
if (config.method?.toLowerCase() === 'get') {
const url = new URL(config.url, window.location.origin);
url.searchParams.set('_t', Date.now());
config.url = url.toString();
}
return config;
}
);
// 响应拦截器:处理通用错误
this.interceptors.response.use(
(response) => response,
async (error) => {
// 处理认证失败
if (error.status === 401) {
// 刷新token或跳转到登录页
await this.handleUnauthorized();
}
// 处理服务器错误
if (error.status >= 500) {
console.error('Server error:', error);
}
return Promise.reject(error);
}
);
}
// 核心请求方法
async request(url, options = {}) {
const requestId = this.generateRequestId(url, options);
// 检查是否已有相同的请求在处理中
if (this.pendingRequests.has(requestId)) {
return this.pendingRequests.get(requestId);
}
// 合并配置
const config = {
url: this.baseURL ? `${this.baseURL}${url}` : url,
...this.defaultOptions,
...options
};
// 执行请求拦截器
let processedConfig = config;
for (const interceptor of this.interceptors.request) {
processedConfig = await interceptor(processedConfig);
}
// 创建请求Promise
const requestPromise = this.executeRequest(processedConfig, requestId);
// 存储pending请求
this.pendingRequests.set(requestId, requestPromise);
try {
const response = await requestPromise;
// 执行响应拦截器
let processedResponse = response;
for (const interceptor of this.interceptors.response) {
processedResponse = await interceptor(processedResponse);
}
return processedResponse;
} catch (error) {
// 执行错误拦截器
for (const interceptor of this.interceptors.error) {
await interceptor(error, processedConfig);
}
throw error;
} finally {
// 清理pending请求
this.pendingRequests.delete(requestId);
}
}
async executeRequest(config, requestId) {
const {
url,
method = 'GET',
headers,
body,
timeout,
signal,
cache: cacheOption,
...restOptions
} = config;
// 检查缓存
if (method.toUpperCase() === 'GET' && cacheOption) {
const cached = this.getFromCache(url, cacheOption);
if (cached) {
return cached;
}
}
// 创建AbortController用于超时控制
const controller = new AbortController();
const abortSignal = signal || controller.signal;
// 设置超时
const timeoutId = timeout ? setTimeout(() => {
controller.abort();
}, timeout) : null;
try {
const fetchOptions = {
method,
headers,
signal: abortSignal,
...restOptions
};
// 处理请求体
if (body) {
if (body instanceof FormData || body instanceof URLSearchParams) {
fetchOptions.body = body;
// FormData会自己设置Content-Type
if (fetchOptions.headers && fetchOptions.headers['Content-Type']) {
delete fetchOptions.headers['Content-Type'];
}
} else if (typeof body === 'object') {
fetchOptions.body = JSON.stringify(body);
} else {
fetchOptions.body = body;
}
}
const response = await fetch(url, fetchOptions);
// 清除超时定时器
if (timeoutId) clearTimeout(timeoutId);
// 解析响应
const contentType = response.headers.get('content-type');
let data;
if (contentType && contentType.includes('application/json')) {
data = await response.json();
} else if (contentType && contentType.includes('text/')) {
data = await response.text();
} else if (contentType && contentType.includes('multipart/form-data')) {
data = await response.formData();
} else {
data = await response.blob();
}
const result = {
data,
status: response.status,
statusText: response.statusText,
headers: response.headers,
config,
requestId
};
// 缓存响应
if (method.toUpperCase() === 'GET' && cacheOption && response.ok) {
this.setCache(url, result, cacheOption);
}
if (!response.ok) {
throw this.createError(response, data, config);
}
return result;
} catch (error) {
// 清除超时定时器
if (timeoutId) clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw this.createError(null, 'Request timeout', config);
}
throw error;
}
}
// 创建错误对象
createError(response, data, config) {
const error = new Error(
response ? `HTTP ${response.status}: ${response.statusText}` : 'Network Error'
);
Object.assign(error, {
config,
response: response ? {
data,
status: response.status,
statusText: response.statusText,
headers: response.headers
} : null,
isHttpError: true
});
return error;
}
// 生成请求ID
generateRequestId(url, options) {
const { method = 'GET', body } = options;
const timestamp = Date.now();
const bodyHash = body ? this.hashString(JSON.stringify(body)) : '';
return `${method}_${url}_${bodyHash}_${timestamp}`;
}
// 简单的字符串哈希
hashString(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return hash.toString(36);
}
// HTTP方法快捷方式
get(url, options = {}) {
return this.request(url, { ...options, method: 'GET' });
}
post(url, data, options = {}) {
return this.request(url, { ...options, method: 'POST', body: data });
}
put(url, data, options = {}) {
return this.request(url, { ...options, method: 'PUT', body: data });
}
patch(url, data, options = {}) {
return this.request(url, { ...options, method: 'PATCH', body: data });
}
delete(url, options = {}) {
return this.request(url, { ...options, method: 'DELETE' });
}
head(url, options = {}) {
return this.request(url, { ...options, method: 'HEAD' });
}
options(url, options = {}) {
return this.request(url, { ...options, method: 'OPTIONS' });
}
// 拦截器管理
interceptors = {
request: {
handlers: [],
use(handler) {
this.handlers.push(handler);
return () => {
const index = this.handlers.indexOf(handler);
if (index > -1) this.handlers.splice(index, 1);
};
},
eject(handler) {
const index = this.handlers.indexOf(handler);
if (index > -1) this.handlers.splice(index, 1);
}
},
response: {
handlers: [],
use(onFulfilled, onRejected) {
this.handlers.push({ onFulfilled, onRejected });
return () => {
const index = this.handlers.findIndex(h =>
h.onFulfilled === onFulfilled && h.onRejected === onRejected
);
if (index > -1) this.handlers.splice(index, 1);
};
},
eject(handler) {
const index = this.handlers.findIndex(h =>
h.onFulfilled === handler.onFulfilled &&
h.onRejected === handler.onRejected
);
if (index > -1) this.handlers.splice(index, 1);
}
},
error: {
handlers: [],
use(handler) {
this.handlers.push(handler);
return () => {
const index = this.handlers.indexOf(handler);
if (index > -1) this.handlers.splice(index, 1);
};
},
eject(handler) {
const index = this.handlers.indexOf(handler);
if (index > -1) this.handlers.splice(index, 1);
}
}
};
// 缓存管理
getFromCache(url, cacheOption) {
if (!this.cache.has(url)) return null;
const cached = this.cache.get(url);
const now = Date.now();
// 检查是否过期
if (cacheOption === 'no-cache' ||
(cached.expiry && cached.expiry < now)) {
this.cache.delete(url);
return null;
}
return cached.data;
}
setCache(url, data, cacheOption) {
let expiry = null;
if (typeof cacheOption === 'number') {
expiry = Date.now() + cacheOption; // 毫秒
} else if (cacheOption === 'short') {
expiry = Date.now() + (5 * 60 * 1000); // 5分钟
} else if (cacheOption === 'long') {
expiry = Date.now() + (60 * 60 * 1000); // 1小时
}
this.cache.set(url, { data, expiry });
// 清理过期缓存
this.cleanupCache();
}
cleanupCache() {
const now = Date.now();
for (const [url, cached] of this.cache.entries()) {
if (cached.expiry && cached.expiry < now) {
this.cache.delete(url);
}
}
}
// 清除缓存
clearCache(url = null) {
if (url) {
this.cache.delete(url);
} else {
this.cache.clear();
}
}
// 从cookie获取值
getCookie(name) {
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
return match ? decodeURIComponent(match[2]) : null;
}
// 处理未授权
async handleUnauthorized() {
// 尝试刷新token
try {
const refreshToken = localStorage.getItem('refresh_token');
if (refreshToken) {
const response = await this.post('/auth/refresh', {
refresh_token: refreshToken
});
if (response.data.access_token) {
localStorage.setItem('auth_token', response.data.access_token);
return true;
}
}
} catch (error) {
// 刷新失败,清除本地认证信息
localStorage.removeItem('auth_token');
localStorage.removeItem('refresh_token');
// 跳转到登录页
window.location.href = '/login';
}
return false;
}
// 批量请求
async all(requests) {
return Promise.all(requests);
}
// 请求并发控制
async spread(requests, maxConcurrent = this.maxConcurrent) {
const results = [];
let index = 0;
const runNext = async () => {
if (index >= requests.length) return;
const currentIndex = index++;
const request = requests[currentIndex];
try {
const result = await request();
results[currentIndex] = { status: 'fulfilled', value: result };
} catch (error) {
results[currentIndex] = { status: 'rejected', reason: error };
}
// 递归执行下一个
await runNext();
};
// 创建并发执行器
const concurrentPromises = [];
for (let i = 0; i < Math.min(maxConcurrent, requests.length); i++) {
concurrentPromises.push(runNext());
}
await Promise.all(concurrentPromises);
return results;
}
}
2.2 请求拦截器的高级实现
javascript
class AdvancedInterceptor {
constructor(httpClient) {
this.httpClient = httpClient;
this.setupAdvancedInterceptors();
}
setupAdvancedInterceptors() {
// 1. 请求节流/防抖拦截器
this.addThrottleInterceptor();
// 2. 请求合并拦截器
this.addBatchInterceptor();
// 3. 请求重试拦截器
this.addRetryInterceptor();
// 4. 请求缓存拦截器
this.addCacheInterceptor();
// 5. 请求日志拦截器
this.addLoggingInterceptor();
// 6. 性能监控拦截器
this.addPerformanceInterceptor();
}
addThrottleInterceptor() {
const requestTimestamps = new Map();
const requestQueue = new Map();
this.httpClient.interceptors.request.use(
async (config) => {
const { throttle = false, debounce = false, delay = 1000 } = config;
if (!throttle && !debounce) return config;
const requestKey = `${config.method}_${config.url}`;
// 节流处理
if (throttle) {
const lastRequestTime = requestTimestamps.get(requestKey) || 0;
const now = Date.now();
if (now - lastRequestTime < delay) {
// 仍在节流期内,延迟请求
await new Promise(resolve => {
setTimeout(resolve, delay - (now - lastRequestTime));
});
}
requestTimestamps.set(requestKey, Date.now());
}
// 防抖处理
if (debounce) {
return new Promise((resolve) => {
// 清除之前的定时器
if (requestQueue.has(requestKey)) {
clearTimeout(requestQueue.get(requestKey));
}
// 设置新的定时器
const timerId = setTimeout(() => {
requestQueue.delete(requestKey);
resolve(config);
}, delay);
requestQueue.set(requestKey, timerId);
});
}
return config;
}
);
}
addBatchInterceptor() {
const batchRequests = new Map();
const BATCH_DELAY = 100; // 100ms批处理窗口
this.httpClient.interceptors.request.use(
(config) => {
const { batch = false, batchKey } = config;
if (!batch) return config;
const key = batchKey || config.url.split('?')[0];
return new Promise((resolve) => {
if (!batchRequests.has(key)) {
batchRequests.set(key, {
timer: null,
requests: []
});
}
const batchInfo = batchRequests.get(key);
batchInfo.requests.push({ config, resolve });
// 清除之前的定时器
if (batchInfo.timer) {
clearTimeout(batchInfo.timer);
}
// 设置新的定时器
batchInfo.timer = setTimeout(async () => {
const requests = batchInfo.requests;
batchRequests.delete(key);
// 合并请求
const mergedConfig = this.mergeBatchRequests(requests);
const response = await this.httpClient.request(mergedConfig.url, mergedConfig);
// 分发响应
this.distributeBatchResponse(requests, response);
}, BATCH_DELAY);
});
}
);
}
mergeBatchRequests(requests) {
// 简单示例:合并GET请求参数
const firstConfig = requests[0].config;
const url = new URL(firstConfig.url, window.location.origin);
// 收集所有请求的参数
const allParams = [];
requests.forEach(({ config }) => {
const requestUrl = new URL(config.url, window.location.origin);
const params = Object.fromEntries(requestUrl.searchParams.entries());
allParams.push(params);
});
// 设置批处理参数
url.searchParams.set('batch', JSON.stringify(allParams));
return {
...firstConfig,
url: url.toString(),
batch: false // 防止递归
};
}
distributeBatchResponse(requests, batchResponse) {
// 这里需要根据实际API的批处理响应格式来分发
// 假设API返回数组,顺序对应请求顺序
if (Array.isArray(batchResponse.data)) {
requests.forEach(({ resolve }, index) => {
const individualResponse = {
...batchResponse,
data: batchResponse.data[index]
};
resolve(individualResponse);
});
}
}
addRetryInterceptor() {
this.httpClient.interceptors.response.use(
(response) => response,
async (error) => {
const config = error.config || {};
const { retry = this.httpClient.retryConfig, __retryCount = 0 } = config;
// 检查是否应该重试
if (!this.shouldRetry(error, retry, __retryCount)) {
return Promise.reject(error);
}
// 增加重试计数
config.__retryCount = __retryCount + 1;
// 计算延迟时间(指数退避)
const delay = retry.retryDelay * Math.pow(2, __retryCount);
// 等待延迟时间
await new Promise(resolve => setTimeout(resolve, delay));
// 重试请求
return this.httpClient.request(config.url, config);
}
);
}
shouldRetry(error, retryConfig, retryCount) {
// 检查最大重试次数
if (retryCount >= retryConfig.maxRetries) return false;
// 检查错误类型
if (!error.response) {
// 网络错误应该重试
return true;
}
// 检查状态码
const status = error.response.status;
return retryConfig.retryOn.includes(status);
}
addCacheInterceptor() {
this.httpClient.interceptors.request.use(
(config) => {
const { cache = false, forceRefresh = false } = config;
if (!cache || forceRefresh) return config;
// 检查内存缓存
const cached = this.httpClient.getFromCache(config.url, cache);
if (cached) {
// 返回缓存的响应,不再发送请求
return Promise.reject({
config,
response: cached,
isCacheHit: true
});
}
return config;
}
);
// 处理缓存命中
this.httpClient.interceptors.response.use(
(response) => response,
(error) => {
if (error.isCacheHit) {
return Promise.resolve(error.response);
}
return Promise.reject(error);
}
);
}
addLoggingInterceptor() {
// 请求日志
this.httpClient.interceptors.request.use(
(config) => {
console.group(`🌐 Request: ${config.method} ${config.url}`);
console.log('Config:', config);
console.groupEnd();
// 添加请求时间戳
config.__startTime = performance.now();
return config;
}
);
// 响应日志
this.httpClient.interceptors.response.use(
(response) => {
const duration = performance.now() - response.config.__startTime;
console.group(`✅ Response: ${response.status} ${response.config.url}`);
console.log('Response:', response);
console.log(`Duration: ${duration.toFixed(2)}ms`);
console.groupEnd();
// 发送性能数据到监控系统
this.sendMetrics({
url: response.config.url,
method: response.config.method,
status: response.status,
duration,
timestamp: Date.now()
});
return response;
},
(error) => {
const duration = error.config ?
performance.now() - error.config.__startTime : 0;
console.group(`❌ Error: ${error.config?.url}`);
console.log('Error:', error);
console.log(`Duration: ${duration.toFixed(2)}ms`);
console.groupEnd();
// 发送错误数据到监控系统
this.sendErrorMetrics({
url: error.config?.url,
method: error.config?.method,
status: error.response?.status,
duration,
error: error.message,
timestamp: Date.now()
});
return Promise.reject(error);
}
);
}
addPerformanceInterceptor() {
const performanceData = [];
const MAX_PERFORMANCE_ENTRIES = 100;
this.httpClient.interceptors.request.use(
(config) => {
// 使用Performance API标记请求开始
if (window.performance && window.performance.mark) {
const markName = `request_start_${config.url.replace(/[^a-z0-9]/gi, '_')}`;
window.performance.mark(markName);
config.__performanceMark = markName;
}
return config;
}
);
this.httpClient.interceptors.response.use(
(response) => {
// 测量请求性能
if (window.performance && response.config.__performanceMark) {
const markName = response.config.__performanceMark;
const measureName = `request_${response.config.url.replace(/[^a-z0-9]/gi, '_')}`;
window.performance.mark(`${markName}_end`);
window.performance.measure(
measureName,
markName,
`${markName}_end`
);
// 获取测量结果
const measures = window.performance.getEntriesByName(measureName);
if (measures.length > 0) {
const measure = measures[0];
performanceData.push({
url: response.config.url,
duration: measure.duration,
startTime: measure.startTime,
timestamp: Date.now()
});
// 限制数据大小
if (performanceData.length > MAX_PERFORMANCE_ENTRIES) {
performanceData.shift();
}
}
}
return response;
}
);
}
sendMetrics(metric) {
// 发送到监控系统(例如:Google Analytics,自建监控等)
if (window.gtag) {
window.gtag('event', 'request_completed', metric);
}
// 或者发送到后端
if (navigator.sendBeacon) {
const data = new Blob([JSON.stringify(metric)], { type: 'application/json' });
navigator.sendBeacon('/api/metrics', data);
}
}
sendErrorMetrics(errorMetric) {
// 发送错误监控
if (window.Rollbar) {
window.Rollbar.error('HTTP Request Error', errorMetric);
}
// 发送到后端
if (navigator.sendBeacon) {
const data = new Blob([JSON.stringify(errorMetric)], { type: 'application/json' });
navigator.sendBeacon('/api/errors', data);
}
}
// 获取性能报告
getPerformanceReport() {
const totalRequests = performanceData.length;
if (totalRequests === 0) {
return { averageDuration: 0, totalRequests: 0 };
}
const totalDuration = performanceData.reduce((sum, entry) => sum + entry.duration, 0);
const averageDuration = totalDuration / totalRequests;
// 按URL分组
const urlStats = {};
performanceData.forEach(entry => {
if (!urlStats[entry.url]) {
urlStats[entry.url] = {
count: 0,
totalDuration: 0,
durations: []
};
}
urlStats[entry.url].count++;
urlStats[entry.url].totalDuration += entry.duration;
urlStats[entry.url].durations.push(entry.duration);
});
// 计算每个URL的平均值
Object.keys(urlStats).forEach(url => {
const stats = urlStats[url];
stats.averageDuration = stats.totalDuration / stats.count;
// 计算p95
stats.durations.sort((a, b) => a - b);
const p95Index = Math.floor(stats.durations.length * 0.95);
stats.p95 = stats.durations[p95Index];
});
return {
totalRequests,
averageDuration,
urlStats,
recentRequests: performanceData.slice(-10)
};
}
}
三、请求取消与并发控制
3.1 高级请求取消机制
javascript
class RequestCancellation {
constructor() {
this.cancelTokens = new Map();
this.cancelSources = new Map();
this.requestGroups = new Map();
}
// 创建取消令牌
createCancelToken() {
const source = {
token: null,
cancel: null
};
const token = new Promise((resolve, reject) => {
source.cancel = (reason) => {
reject(new RequestCancellationError(reason || 'Request cancelled'));
};
});
source.token = token;
const tokenId = this.generateTokenId();
this.cancelSources.set(tokenId, source);
return {
token,
cancel: source.cancel,
tokenId
};
}
// 为请求添加取消支持
withCancellation(requestPromise, cancelToken, requestId) {
if (!cancelToken) return requestPromise;
const abortController = new AbortController();
const signal = abortController.signal;
// 包装请求以支持取消
const wrappedPromise = Promise.race([
requestPromise,
cancelToken.then(
() => Promise.reject(new RequestCancellationError('Request cancelled by token'))),
new Promise((_, reject) => {
signal.addEventListener('abort', () => {
reject(new RequestCancellationError('Request aborted'));
});
})
]);
// 存储取消控制器
this.cancelTokens.set(requestId, abortController);
// 清理函数
wrappedPromise.finally(() => {
this.cancelTokens.delete(requestId);
});
return wrappedPromise;
}
// 取消特定请求
cancelRequest(requestId, reason) {
const controller = this.cancelTokens.get(requestId);
if (controller) {
controller.abort(reason);
this.cancelTokens.delete(requestId);
return true;
}
return false;
}
// 取消一组请求
cancelGroup(groupId, reason) {
const group = this.requestGroups.get(groupId);
if (!group) return false;
group.forEach(requestId => {
this.cancelRequest(requestId, reason);
});
this.requestGroups.delete(groupId);
return true;
}
// 取消所有请求
cancelAll(reason) {
Array.from(this.cancelTokens.keys()).forEach(requestId => {
this.cancelRequest(requestId, reason);
});
Array.from(this.requestGroups.keys()).forEach(groupId => {
this.cancelGroup(groupId, reason);
});
return true;
}
// 将请求添加到组
addToGroup(requestId, groupId) {
if (!this.requestGroups.has(groupId)) {
this.requestGroups.set(groupId, new Set());
}
this.requestGroups.get(groupId).add(requestId);
// 返回移除函数
return () => {
const group = this.requestGroups.get(groupId);
if (group) {
group.delete(requestId);
if (group.size === 0) {
this.requestGroups.delete(groupId);
}
}
};
}
// 生成令牌ID
generateTokenId() {
return `token_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
// 创建竞速请求(第一个成功或全部失败)
createRace(requests, options = {}) {
const {
cancelPrevious = true,
groupId = `race_${Date.now()}`
} = options;
// 如果取消之前的请求
if (cancelPrevious) {
this.cancelGroup(groupId, 'Superseded by new request');
}
const racePromise = Promise.race(requests);
// 为每个请求添加到组
requests.forEach((request, index) => {
const requestId = `${groupId}_${index}`;
this.addToGroup(requestId, groupId);
// 请求完成后从组中移除
request.then(
() => this.removeFromGroup(requestId, groupId),
() => this.removeFromGroup(requestId, groupId)
);
});
return racePromise;
}
// 从组中移除请求
removeFromGroup(requestId, groupId) {
const group = this.requestGroups.get(groupId);
if (group) {
group.delete(requestId);
if (group.size === 0) {
this.requestGroups.delete(groupId);
}
}
}
// 创建请求池
createRequestPool(maxConcurrent = 5) {
const queue = [];
let activeCount = 0;
const runNext = async () => {
if (activeCount >= maxConcurrent || queue.length === 0) {
return;
}
activeCount++;
const { request, resolve, reject } = queue.shift();
try {
const result = await request();
resolve(result);
} catch (error) {
reject(error);
} finally {
activeCount--;
runNext();
}
};
const enqueue = (request) => {
return new Promise((resolve, reject) => {
queue.push({ request, resolve, reject });
runNext();
});
};
return {
enqueue,
getQueueSize: () => queue.length,
getActiveCount: () => activeCount
};
}
// 批量请求的并发控制
async batchWithConcurrency(requests, concurrency = 3) {
const results = new Array(requests.length);
let currentIndex = 0;
const executeNext = async () => {
if (currentIndex >= requests.length) return;
const index = currentIndex++;
const request = requests[index];
try {
results[index] = await request();
} catch (error) {
results[index] = error;
}
// 执行下一个
await executeNext();
};
// 创建并发的执行器
const concurrentPromises = [];
for (let i = 0; i < Math.min(concurrency, requests.length); i++) {
concurrentPromises.push(executeNext());
}
await Promise.all(concurrentPromises);
return results;
}
}
// 自定义取消错误
class RequestCancellationError extends Error {
constructor(message = 'Request cancelled') {
super(message);
this.name = 'RequestCancellationError';
this.isCancelled = true;
}
}
// 使用示例
class CancellationExample {
static async demonstrate() {
const cancellation = new RequestCancellation();
// 创建取消令牌
const { token: cancelToken, cancel } = cancellation.createCancelToken();
// 创建请求
const request1 = fetch('https://api.example.com/data1');
const request2 = fetch('https://api.example.com/data2');
// 包装请求以支持取消
const cancellableRequest1 = cancellation.withCancellation(
request1,
cancelToken,
'request_1'
);
const cancellableRequest2 = cancellation.withCancellation(
request2,
cancelToken,
'request_2'
);
// 将请求添加到组
cancellation.addToGroup('request_1', 'data_fetch_group');
cancellation.addToGroup('request_2', 'data_fetch_group');
// 模拟3秒后取消所有请求
setTimeout(() => {
cancellation.cancelGroup('data_fetch_group', 'User cancelled');
}, 3000);
try {
const results = await Promise.all([
cancellableRequest1,
cancellableRequest2
]);
console.log('Requests completed:', results);
} catch (error) {
if (error.isCancelled) {
console.log('Requests were cancelled:', error.message);
} else {
console.error('Request failed:', error);
}
}
// 使用请求池
const pool = cancellation.createRequestPool(2);
const requests = [
() => fetch('https://api.example.com/item/1'),
() => fetch('https://api.example.com/item/2'),
() => fetch('https://api.example.com/item/3'),
() => fetch('https://api.example.com/item/4'),
() => fetch('https://api.example.com/item/5')
];
const poolResults = await Promise.all(
requests.map(request => pool.enqueue(request))
);
console.log('Pool results:', poolResults);
}
}
3.2 智能请求调度器
javascript
class IntelligentRequestScheduler {
constructor(options = {}) {
this.options = {
maxConcurrent: 6,
priorityLevels: ['high', 'normal', 'low'],
retryEnabled: true,
cacheEnabled: true,
offlineQueueEnabled: true,
...options
};
this.queues = {
high: [],
normal: [],
low: []
};
this.activeRequests = new Set();
this.requestCache = new Map();
this.offlineQueue = [];
this.isOnline = navigator.onLine;
this.retryStrategies = new Map();
this.requestStats = new Map();
this.init();
}
init() {
// 监听网络状态
window.addEventListener('online', () => {
this.isOnline = true;
this.processOfflineQueue();
});
window.addEventListener('offline', () => {
this.isOnline = false;
});
// 启动调度器
this.startScheduler();
// 初始化重试策略
this.initRetryStrategies();
}
startScheduler() {
setInterval(() => {
this.processQueues();
}, 100); // 每100ms处理一次队列
}
initRetryStrategies() {
// 默认重试策略
this.retryStrategies.set('default', {
maxRetries: 3,
baseDelay: 1000,
maxDelay: 10000,
backoffMultiplier: 2,
retryableStatuses: [408, 429, 500, 502, 503, 504],
retryableErrors: ['ECONNABORTED', 'ETIMEDOUT', 'NETWORK_ERROR']
});
// 关键请求的重试策略
this.retryStrategies.set('critical', {
maxRetries: 5,
baseDelay: 500,
maxDelay: 30000,
backoffMultiplier: 1.5,
retryableStatuses: [408, 429, 500, 502, 503, 504, 502],
retryableErrors: ['ECONNABORTED', 'ETIMEDOUT', 'NETWORK_ERROR']
});
// 非关键请求的重试策略
this.retryStrategies.set('non-critical', {
maxRetries: 1,
baseDelay: 2000,
maxDelay: 5000,
backoffMultiplier: 2,
retryableStatuses: [500, 502, 503, 504],
retryableErrors: ['NETWORK_ERROR']
});
}
// 调度请求
schedule(request, options = {}) {
const {
priority = 'normal',
retryStrategy = 'default',
cacheKey = null,
offlineFallback = false,
timeout = 30000,
group = null
} = options;
const requestId = this.generateRequestId(request, options);
const requestInfo = {
id: requestId,
request,
options,
priority,
retryStrategy,
cacheKey,
offlineFallback,
timeout,
group,
status: 'pending',
retryCount: 0,
createdAt: Date.now(),
scheduledAt: null,
completedAt: null
};
// 检查缓存
if (this.options.cacheEnabled && cacheKey) {
const cached = this.requestCache.get(cacheKey);
if (cached && !this.isCacheExpired(cached)) {
requestInfo.status = 'cached';
requestInfo.result = cached.data;
return Promise.resolve(cached.data);
}
}
// 添加到相应优先级的队列
this.queues[priority].push(requestInfo);
// 如果是离线状态且启用了离线队列
if (!this.isOnline && this.options.offlineQueueEnabled && offlineFallback) {
this.offlineQueue.push(requestInfo);
return Promise.reject(new Error('Offline - request queued'));
}
// 返回Promise
return new Promise((resolve, reject) => {
requestInfo.resolve = resolve;
requestInfo.reject = reject;
// 记录请求统计
this.recordRequestStat(requestInfo);
});
}
// 处理队列
processQueues() {
// 检查是否有空闲槽位
const availableSlots = this.options.maxConcurrent - this.activeRequests.size;
if (availableSlots <= 0) return;
// 按优先级处理队列
for (const priority of this.options.priorityLevels) {
const queue = this.queues[priority];
while (queue.length > 0 && availableSlots > 0) {
const requestInfo = queue.shift();
// 跳过已取消的请求
if (requestInfo.status === 'cancelled') continue;
// 标记为调度中
requestInfo.status = 'scheduled';
requestInfo.scheduledAt = Date.now();
// 执行请求
this.executeRequest(requestInfo);
}
}
}
// 执行请求
async executeRequest(requestInfo) {
this.activeRequests.add(requestInfo.id);
try {
// 设置超时
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(`Request timeout after ${requestInfo.timeout}ms`));
}, requestInfo.timeout);
});
// 执行请求
const requestPromise = requestInfo.request();
const result = await Promise.race([requestPromise, timeoutPromise]);
// 请求成功
requestInfo.status = 'completed';
requestInfo.completedAt = Date.now();
requestInfo.result = result;
// 缓存结果
if (this.options.cacheEnabled && requestInfo.cacheKey) {
this.requestCache.set(requestInfo.cacheKey, {
data: result,
timestamp: Date.now(),
ttl: requestInfo.options.cacheTTL || 300000 // 默认5分钟
});
}
// 解析Promise
if (requestInfo.resolve) {
requestInfo.resolve(result);
}
// 清理
this.cleanupRequest(requestInfo);
} catch (error) {
// 处理错误
await this.handleRequestError(requestInfo, error);
} finally {
this.activeRequests.delete(requestInfo.id);
}
}
// 处理请求错误
async handleRequestError(requestInfo, error) {
requestInfo.lastError = error;
// 检查是否需要重试
if (this.options.retryEnabled && this.shouldRetry(requestInfo, error)) {
requestInfo.retryCount++;
requestInfo.status = 'retrying';
// 计算重试延迟
const delay = this.calculateRetryDelay(requestInfo);
// 延迟后重新加入队列
setTimeout(() => {
requestInfo.status = 'pending';
this.queues[requestInfo.priority].push(requestInfo);
}, delay);
return;
}
// 重试次数用完或不需要重试
requestInfo.status = 'failed';
requestInfo.completedAt = Date.now();
// 如果是离线相关错误,加入离线队列
if (!this.isOnline && requestInfo.offlineFallback) {
this.offlineQueue.push(requestInfo);
}
// 拒绝Promise
if (requestInfo.reject) {
requestInfo.reject(error);
}
// 清理
this.cleanupRequest(requestInfo);
}
// 检查是否需要重试
shouldRetry(requestInfo, error) {
const strategy = this.retryStrategies.get(requestInfo.retryStrategy) ||
this.retryStrategies.get('default');
// 检查重试次数
if (requestInfo.retryCount >= strategy.maxRetries) {
return false;
}
// 检查错误类型
const errorType = this.getErrorType(error);
if (strategy.retryableErrors.includes(errorType)) {
return true;
}
// 检查HTTP状态码
if (error.response && error.response.status) {
return strategy.retryableStatuses.includes(error.response.status);
}
return false;
}
// 计算重试延迟(指数退避)
calculateRetryDelay(requestInfo) {
const strategy = this.retryStrategies.get(requestInfo.retryStrategy) ||
this.retryStrategies.get('default');
const baseDelay = strategy.baseDelay;
const multiplier = strategy.backoffMultiplier;
const maxDelay = strategy.maxDelay;
const delay = baseDelay * Math.pow(multiplier, requestInfo.retryCount - 1);
// 添加随机抖动(防止惊群效应)
const jitter = delay * 0.1 * (Math.random() * 2 - 1);
return Math.min(delay + jitter, maxDelay);
}
// 处理离线队列
async processOfflineQueue() {
if (!this.isOnline || this.offlineQueue.length === 0) return;
console.log(`Processing ${this.offlineQueue.length} queued offline requests`);
// 复制队列并清空原队列
const queueToProcess = [...this.offlineQueue];
this.offlineQueue = [];
// 处理队列中的请求
for (const requestInfo of queueToProcess) {
requestInfo.status = 'pending';
this.queues[requestInfo.priority].push(requestInfo);
}
}
// 清理请求
cleanupRequest(requestInfo) {
// 更新统计信息
this.updateRequestStats(requestInfo);
// 从统计中移除旧记录
this.cleanupOldStats();
}
// 记录请求统计
recordRequestStat(requestInfo) {
if (!this.requestStats.has(requestInfo.id)) {
this.requestStats.set(requestInfo.id, {
id: requestInfo.id,
priority: requestInfo.priority,
createdAt: requestInfo.createdAt,
completedAt: null,
duration: null,
status: 'pending',
retryCount: 0,
group: requestInfo.group
});
}
}
// 更新请求统计
updateRequestStats(requestInfo) {
const stat = this.requestStats.get(requestInfo.id);
if (stat) {
stat.status = requestInfo.status;
stat.retryCount = requestInfo.retryCount;
stat.completedAt = requestInfo.completedAt;
if (requestInfo.completedAt && requestInfo.createdAt) {
stat.duration = requestInfo.completedAt - requestInfo.createdAt;
}
}
}
// 清理旧统计
cleanupOldStats() {
const oneHourAgo = Date.now() - (60 * 60 * 1000);
for (const [id, stat] of this.requestStats.entries()) {
if (stat.createdAt < oneHourAgo) {
this.requestStats.delete(id);
}
}
}
// 生成请求ID
generateRequestId(request, options) {
const requestString = request.toString();
const optionsString = JSON.stringify(options);
return `req_${Date.now()}_${this.hashString(requestString + optionsString)}`;
}
// 简单的哈希函数
hashString(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return hash.toString(36);
}
// 获取错误类型
getErrorType(error) {
if (error.code) return error.code;
if (error.name) return error.name;
if (error.message && error.message.includes('timeout')) return 'ETIMEDOUT';
if (error.message && error.message.includes('network')) return 'NETWORK_ERROR';
return 'UNKNOWN_ERROR';
}
// 检查缓存是否过期
isCacheExpired(cacheEntry) {
if (!cacheEntry.ttl) return false;
return Date.now() > cacheEntry.timestamp + cacheEntry.ttl;
}
// 获取调度器状态
getStatus() {
const pendingRequests = Object.values(this.queues)
.reduce((sum, queue) => sum + queue.length, 0);
const stats = Array.from(this.requestStats.values());
const successCount = stats.filter(s => s.status === 'completed').length;
const failedCount = stats.filter(s => s.status === 'failed').length;
const retryCount = stats.reduce((sum, s) => sum + s.retryCount, 0);
const avgDuration = stats
.filter(s => s.duration)
.reduce((sum, s) => sum + s.duration, 0) /
Math.max(1, stats.filter(s => s.duration).length);
return {
isOnline: this.isOnline,
activeRequests: this.activeRequests.size,
pendingRequests,
offlineQueue: this.offlineQueue.length,
cacheSize: this.requestCache.size,
totalProcessed: stats.length,
successRate: stats.length > 0 ? (successCount / stats.length) * 100 : 0,
averageRetries: stats.length > 0 ? (retryCount / stats.length) : 0,
averageDuration: avgDuration,
queueSizes: {
high: this.queues.high.length,
normal: this.queues.normal.length,
low: this.queues.low.length
}
};
}
// 取消请求
cancelRequest(requestId) {
// 查找并标记请求为取消状态
for (const priority of this.options.priorityLevels) {
const queue = this.queues[priority];
const index = queue.findIndex(req => req.id === requestId);
if (index !== -1) {
const requestInfo = queue[index];
requestInfo.status = 'cancelled';
if (requestInfo.reject) {
requestInfo.reject(new RequestCancellationError('Request cancelled'));
}
queue.splice(index, 1);
return true;
}
}
// 检查离线队列
const offlineIndex = this.offlineQueue.findIndex(req => req.id === requestId);
if (offlineIndex !== -1) {
const requestInfo = this.offlineQueue[offlineIndex];
requestInfo.status = 'cancelled';
if (requestInfo.reject) {
requestInfo.reject(new RequestCancellationError('Request cancelled'));
}
this.offlineQueue.splice(offlineIndex, 1);
return true;
}
return false;
}
// 清空缓存
clearCache() {
this.requestCache.clear();
}
}
// 使用示例
class SchedulerExample {
static async demonstrate() {
const scheduler = new IntelligentRequestScheduler({
maxConcurrent: 3,
priorityLevels: ['high', 'normal', 'low']
});
// 创建一些请求函数
const createRequest = (id, delay = 1000) => async () => {
console.log(`Starting request ${id}`);
await new Promise(resolve => setTimeout(resolve, delay));
console.log(`Completed request ${id}`);
return { id, data: `Result from request ${id}` };
};
// 调度不同优先级的请求
const requests = [
scheduler.schedule(createRequest('high_1', 500), {
priority: 'high',
retryStrategy: 'critical',
cacheKey: 'request_high_1'
}),
scheduler.schedule(createRequest('normal_1', 1000), {
priority: 'normal',
cacheKey: 'request_normal_1'
}),
scheduler.schedule(createRequest('low_1', 1500), {
priority: 'low',
retryStrategy: 'non-critical'
}),
scheduler.schedule(createRequest('high_2', 200), {
priority: 'high',
offlineFallback: true
})
];
try {
const results = await Promise.all(requests);
console.log('All requests completed:', results);
} catch (error) {
console.error('Some requests failed:', error);
}
// 获取调度器状态
const status = scheduler.getStatus();
console.log('Scheduler status:', status);
return scheduler;
}
}
四、WebSocket高级封装
4.1 完整的WebSocket客户端
javascript
class WebSocketClient {
constructor(url, options = {}) {
this.url = url;
this.options = {
protocols: [],
reconnect: true,
maxReconnectAttempts: 5,
reconnectDelay: 1000,
heartbeatEnabled: true,
heartbeatInterval: 30000,
autoConnect: true,
binaryType: 'blob',
...options
};
this.socket = null;
this.reconnectAttempts = 0;
this.messageQueue = [];
this.listeners = new Map();
this.heartbeatInterval = null;
this.reconnectTimeout = null;
this.isConnected = false;
this.isConnecting = false;
this.connectionId = null;
// 事件类型
this.EVENTS = {
OPEN: 'open',
CLOSE: 'close',
MESSAGE: 'message',
ERROR: 'error',
RECONNECT: 'reconnect',
HEARTBEAT: 'heartbeat'
};
if (this.options.autoConnect) {
this.connect();
}
}
// 连接WebSocket
connect() {
if (this.isConnected || this.isConnecting) {
console.warn('WebSocket is already connected or connecting');
return;
}
this.isConnecting = true;
try {
this.socket = new WebSocket(this.url, this.options.protocols);
this.socket.binaryType = this.options.binaryType;
this.setupEventListeners();
// 设置连接超时
setTimeout(() => {
if (this.isConnecting && !this.isConnected) {
console.warn('WebSocket connection timeout');
this.handleConnectionError(new Error('Connection timeout'));
}
}, 10000);
} catch (error) {
this.handleConnectionError(error);
}
}
// 设置事件监听器
setupEventListeners() {
this.socket.onopen = (event) => {
this.isConnected = true;
this.isConnecting = false;
this.reconnectAttempts = 0;
this.connectionId = this.generateConnectionId();
console.log(`WebSocket connected: ${this.url}`);
// 触发open事件
this.emit(this.EVENTS.OPEN, {
connectionId: this.connectionId,
event
});
// 启动心跳检测
if (this.options.heartbeatEnabled) {
this.startHeartbeat();
}
// 发送队列中的消息
this.flushMessageQueue();
};
this.socket.onclose = (event) => {
this.isConnected = false;
this.isConnecting = false;
console.log(`WebSocket closed: ${event.code} ${event.reason}`);
// 触发close事件
this.emit(this.EVENTS.CLOSE, {
code: event.code,
reason: event.reason,
wasClean: event.wasClean,
connectionId: this.connectionId
});
// 停止心跳检测
this.stopHeartbeat();
// 尝试重连
if (this.options.reconnect && !event.wasClean) {
this.attemptReconnect();
}
};
this.socket.onmessage = (event) => {
// 处理心跳响应
if (this.isHeartbeatMessage(event.data)) {
this.emit(this.EVENTS.HEARTBEAT, { timestamp: Date.now() });
return;
}
// 触发message事件
this.emit(this.EVENTS.MESSAGE, {
data: event.data,
origin: event.origin,
connectionId: this.connectionId,
timestamp: Date.now()
});
};
this.socket.onerror = (event) => {
console.error('WebSocket error:', event);
// 触发error事件
this.emit(this.EVENTS.ERROR, {
error: event,
connectionId: this.connectionId
});
this.handleConnectionError(new Error('WebSocket error'));
};
}
// 发送消息
send(data, options = {}) {
const {
queueIfNotConnected = true,
retry = false,
maxRetries = 3,
retryDelay = 1000
} = options;
// 如果未连接且允许排队
if (!this.isConnected && queueIfNotConnected) {
this.messageQueue.push({ data, options, retryCount: 0 });
return Promise.reject(new Error('WebSocket not connected, message queued'));
}
// 如果未连接且不允许排队
if (!this.isConnected && !queueIfNotConnected) {
return Promise.reject(new Error('WebSocket not connected'));
}
return new Promise((resolve, reject) => {
try {
this.socket.send(data);
resolve();
} catch (error) {
if (retry && (options.retryCount || 0) < maxRetries) {
const retryCount = (options.retryCount || 0) + 1;
setTimeout(() => {
this.send(data, { ...options, retryCount })
.then(resolve)
.catch(reject);
}, retryDelay);
} else {
reject(error);
}
}
});
}
// 发送JSON消息
sendJson(data, options = {}) {
const jsonString = JSON.stringify(data);
return this.send(jsonString, options);
}
// 发送带请求-响应模式的消息
async sendWithResponse(data, options = {}) {
const {
timeout = 30000,
responseType = 'json'
} = options;
return new Promise((resolve, reject) => {
// 生成唯一的消息ID
const messageId = this.generateMessageId();
// 创建超时定时器
const timeoutId = setTimeout(() => {
this.off(messageId);
reject(new Error('Response timeout'));
}, timeout);
// 监听特定消息ID的响应
this.once(messageId, (response) => {
clearTimeout(timeoutId);
// 根据类型解析响应
let parsedResponse;
try {
if (responseType === 'json') {
parsedResponse = JSON.parse(response.data);
} else if (responseType === 'text') {
parsedResponse = response.data;
} else {
parsedResponse = response.data;
}
resolve(parsedResponse);
} catch (error) {
reject(new Error(`Failed to parse response: ${error.message}`));
}
});
// 发送消息(包含消息ID)
const message = {
id: messageId,
data,
timestamp: Date.now()
};
this.sendJson(message).catch(reject);
});
}
// 发送RPC调用
async call(method, params, options = {}) {
const rpcRequest = {
jsonrpc: '2.0',
method,
params,
id: this.generateMessageId()
};
return this.sendWithResponse(rpcRequest, options);
}
// 发送二进制数据
sendBinary(data, options = {}) {
let binaryData;
if (data instanceof ArrayBuffer) {
binaryData = data;
} else if (data instanceof Blob) {
binaryData = data;
} else if (ArrayBuffer.isView(data)) {
binaryData = data.buffer;
} else {
throw new Error('Unsupported binary data type');
}
return this.send(binaryData, options);
}
// 处理连接错误
handleConnectionError(error) {
this.isConnecting = false;
this.isConnected = false;
console.error('WebSocket connection error:', error);
if (this.options.reconnect) {
this.attemptReconnect();
}
}
// 尝试重连
attemptReconnect() {
if (this.reconnectAttempts >= this.options.maxReconnectAttempts) {
console.error('Max reconnection attempts reached');
return;
}
this.reconnectAttempts++;
console.log(`Attempting to reconnect (${this.reconnectAttempts}/${this.options.maxReconnectAttempts})...`);
this.emit(this.EVENTS.RECONNECT, {
attempt: this.reconnectAttempts,
maxAttempts: this.options.maxReconnectAttempts
});
// 计算重连延迟(指数退避)
const delay = this.options.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
// 添加随机抖动
const jitter = delay * 0.1 * (Math.random() * 2 - 1);
this.reconnectTimeout = setTimeout(() => {
this.connect();
}, delay + jitter);
}
// 启动心跳检测
startHeartbeat() {
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
}
this.heartbeatInterval = setInterval(() => {
if (this.isConnected) {
this.send('__HEARTBEAT__').catch((error) => {
console.warn('Heartbeat failed:', error);
this.handleConnectionError(new Error('Heartbeat failed'));
});
}
}, this.options.heartbeatInterval);
}
// 停止心跳检测
stopHeartbeat() {
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
this.heartbeatInterval = null;
}
}
// 检查是否是心跳消息
isHeartbeatMessage(data) {
return data === '__HEARTBEAT__' || data === '__HEARTBEAT_RESPONSE__';
}
// 清空消息队列
flushMessageQueue() {
while (this.messageQueue.length > 0) {
const { data, options, retryCount } = this.messageQueue.shift();
this.send(data, { ...options, retryCount }).catch((error) => {
console.warn('Failed to send queued message:', error);
});
}
}
// 断开连接
disconnect(code = 1000, reason = 'Normal closure') {
// 清除重连定时器
if (this.reconnectTimeout) {
clearTimeout(this.reconnectTimeout);
this.reconnectTimeout = null;
}
// 停止心跳检测
this.stopHeartbeat();
// 关闭WebSocket
if (this.socket) {
this.socket.close(code, reason);
}
this.isConnected = false;
this.isConnecting = false;
}
// 事件监听
on(event, listener) {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
this.listeners.get(event).add(listener);
// 返回取消监听的函数
return () => this.off(event, listener);
}
// 一次性事件监听
once(event, listener) {
const onceListener = (...args) => {
this.off(event, onceListener);
listener(...args);
};
return this.on(event, onceListener);
}
// 移除事件监听
off(event, listener) {
if (this.listeners.has(event)) {
this.listeners.get(event).delete(listener);
if (this.listeners.get(event).size === 0) {
this.listeners.delete(event);
}
}
}
// 触发事件
emit(event, data) {
if (this.listeners.has(event)) {
this.listeners.get(event).forEach(listener => {
try {
listener(data);
} catch (error) {
console.error(`Error in event listener for "${event}":`, error);
}
});
}
// 触发通配符监听器
if (this.listeners.has('*')) {
this.listeners.get('*').forEach(listener => {
try {
listener(event, data);
} catch (error) {
console.error('Error in wildcard event listener:', error);
}
});
}
}
// 生成连接ID
generateConnectionId() {
return `conn_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
// 生成消息ID
generateMessageId() {
return `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
// 获取连接状态
getStatus() {
return {
url: this.url,
isConnected: this.isConnected,
isConnecting: this.isConnecting,
connectionId: this.connectionId,
reconnectAttempts: this.reconnectAttempts,
maxReconnectAttempts: this.options.maxReconnectAttempts,
queuedMessages: this.messageQueue.length,
binaryType: this.options.binaryType
};
}
// 设置二进制类型
setBinaryType(binaryType) {
this.options.binaryType = binaryType;
if (this.socket) {
this.socket.binaryType = binaryType;
}
}
}
// WebSocket管理类(多连接管理)
class WebSocketManager {
constructor() {
this.connections = new Map();
this.defaultOptions = {
reconnect: true,
maxReconnectAttempts: 3,
reconnectDelay: 1000,
heartbeatEnabled: true,
heartbeatInterval: 30000
};
}
// 创建连接
createConnection(url, options = {}) {
const connectionId = this.generateConnectionId(url);
if (this.connections.has(connectionId)) {
console.warn(`Connection already exists: ${connectionId}`);
return this.connections.get(connectionId);
}
const mergedOptions = { ...this.defaultOptions, ...options };
const client = new WebSocketClient(url, mergedOptions);
this.connections.set(connectionId, client);
// 监听连接关闭事件以清理
client.on('close', () => {
// 可以在这里执行清理操作
});
return client;
}
// 获取连接
getConnection(connectionId) {
return this.connections.get(connectionId);
}
// 关闭连接
closeConnection(connectionId, code = 1000, reason = 'Normal closure') {
const client = this.connections.get(connectionId);
if (client) {
client.disconnect(code, reason);
this.connections.delete(connectionId);
return true;
}
return false;
}
// 关闭所有连接
closeAllConnections(code = 1000, reason = 'Normal closure') {
this.connections.forEach((client, connectionId) => {
client.disconnect(code, reason);
});
this.connections.clear();
}
// 广播消息到所有连接
broadcast(data, options = {}) {
const promises = [];
this.connections.forEach(client => {
if (client.isConnected) {
promises.push(client.send(data, options));
}
});
return Promise.allSettled(promises);
}
// 生成连接ID
generateConnectionId(url) {
const urlObj = new URL(url);
const host = urlObj.host;
const path = urlObj.pathname;
return `${host}${path}`.replace(/[^a-z0-9]/gi, '_');
}
// 获取连接状态
getStatus() {
const status = {
totalConnections: this.connections.size,
connectedConnections: 0,
connectingConnections: 0,
connections: []
};
this.connections.forEach((client, connectionId) => {
const clientStatus = client.getStatus();
if (clientStatus.isConnected) {
status.connectedConnections++;
} else if (clientStatus.isConnecting) {
status.connectingConnections++;
}
status.connections.push({
connectionId,
...clientStatus
});
});
return status;
}
}
// 使用示例
class WebSocketExample {
static demonstrate() {
// 创建WebSocket客户端
const wsClient = new WebSocketClient('wss://echo.websocket.org', {
reconnect: true,
maxReconnectAttempts: 3,
heartbeatEnabled: true
});
// 监听事件
wsClient.on('open', (event) => {
console.log('WebSocket opened:', event.connectionId);
// 发送消息
wsClient.sendJson({ type: 'greeting', message: 'Hello WebSocket!' });
});
wsClient.on('message', (event) => {
console.log('Received message:', event.data);
});
wsClient.on('close', (event) => {
console.log('WebSocket closed:', event.code, event.reason);
});
wsClient.on('error', (event) => {
console.error('WebSocket error:', event.error);
});
// 发送请求-响应模式的消息
wsClient.sendWithResponse({ type: 'ping' })
.then(response => console.log('Response:', response))
.catch(error => console.error('Request failed:', error));
// 获取状态
const status = wsClient.getStatus();
console.log('WebSocket status:', status);
return wsClient;
}
}
4.2 WebSocket重连与消息可靠性
javascript
class ReliableWebSocket extends WebSocketClient {
constructor(url, options = {}) {
const defaultOptions = {
messageRetention: true,
maxMessageQueueSize: 1000,
messageExpiry: 5 * 60 * 1000, // 5分钟
deliveryGuarantee: 'at-least-once', // 'at-most-once', 'at-least-once', 'exactly-once'
requireAcknowledgement: false,
acknowledgementTimeout: 5000,
...options
};
super(url, defaultOptions);
this.pendingMessages = new Map();
this.acknowledgedMessages = new Set();
this.messageSequence = 0;
this.lastAcknowledgedSequence = 0;
this.acknowledgementTimeouts = new Map();
// 设置消息确认监听
this.setupAcknowledgementListener();
}
// 发送可靠消息
async sendReliable(data, options = {}) {
const {
requireAcknowledgement = this.options.requireAcknowledgement,
acknowledgementTimeout = this.options.acknowledgementTimeout,
maxRetries = 3,
retryDelay = 1000
} = options;
// 生成消息ID和序列号
const messageId = this.generateMessageId();
const sequenceNumber = this.messageSequence++;
// 创建消息对象
const message = {
id: messageId,
seq: sequenceNumber,
data,
timestamp: Date.now(),
retryCount: 0,
status: 'pending'
};
// 如果需要确认,添加到待处理消息列表
if (requireAcknowledgement) {
this.pendingMessages.set(messageId, message);
// 设置确认超时
this.setAcknowledgementTimeout(messageId, acknowledgementTimeout);
}
// 封装发送的消息
const wrappedMessage = {
type: 'data',
reliable: requireAcknowledgement,
messageId,
sequenceNumber,
data
};
try {
await this.sendJson(wrappedMessage);
message.status = 'sent';
// 如果需要确认,等待确认
if (requireAcknowledgement) {
return this.waitForAcknowledgement(messageId, acknowledgementTimeout);
}
return Promise.resolve({ messageId, status: 'sent' });
} catch (error) {
message.status = 'failed';
message.error = error;
// 重试逻辑
if (maxRetries > 0) {
return this.retrySendReliable(message, {
maxRetries,
retryDelay,
requireAcknowledgement
});
}
throw error;
}
}
// 等待消息确认
waitForAcknowledgement(messageId, timeout) {
return new Promise((resolve, reject) => {
// 检查是否已经确认
if (this.acknowledgedMessages.has(messageId)) {
resolve({ messageId, status: 'acknowledged' });
return;
}
// 设置超时
const timeoutId = setTimeout(() => {
const message = this.pendingMessages.get(messageId);
if (message) {
message.status = 'timeout';
this.pendingMessages.delete(messageId);
}
reject(new Error(`Acknowledgement timeout for message ${messageId}`));
}, timeout);
// 监听确认事件
const acknowledgementListener = (ack) => {
if (ack.messageId === messageId) {
clearTimeout(timeoutId);
this.off('acknowledgement', acknowledgementListener);
resolve({ messageId, status: 'acknowledged' });
}
};
this.on('acknowledgement', acknowledgementListener);
});
}
// 重试发送可靠消息
async retrySendReliable(message, options) {
const {
maxRetries,
retryDelay,
requireAcknowledgement
} = options;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
message.retryCount = attempt;
message.status = 'retrying';
console.log(`Retrying message ${message.id} (attempt ${attempt}/${maxRetries})`);
// 等待重试延迟
await new Promise(resolve => setTimeout(resolve, retryDelay * attempt));
try {
const wrappedMessage = {
type: 'data',
reliable: requireAcknowledgement,
messageId: message.id,
sequenceNumber: message.seq,
data: message.data,
retry: attempt
};
await this.sendJson(wrappedMessage);
message.status = 'sent';
// 如果需要确认,等待确认
if (requireAcknowledgement) {
const result = await this.waitForAcknowledgement(
message.id,
this.options.acknowledgementTimeout
);
return result;
}
return { messageId: message.id, status: 'sent' };
} catch (error) {
message.status = 'failed';
message.error = error;
if (attempt === maxRetries) {
throw error;
}
}
}
throw new Error(`Failed to send message after ${maxRetries} retries`);
}
// 设置确认超时
setAcknowledgementTimeout(messageId, timeout) {
const timeoutId = setTimeout(() => {
const message = this.pendingMessages.get(messageId);
if (message && message.status !== 'acknowledged') {
console.warn(`Acknowledgement timeout for message ${messageId}`);
// 触发超时事件
this.emit('acknowledgement_timeout', {
messageId,
message,
timestamp: Date.now()
});
this.pendingMessages.delete(messageId);
}
this.acknowledgementTimeouts.delete(messageId);
}, timeout);
this.acknowledgementTimeouts.set(messageId, timeoutId);
}
// 清理确认超时
clearAcknowledgementTimeout(messageId) {
const timeoutId = this.acknowledgementTimeouts.get(messageId);
if (timeoutId) {
clearTimeout(timeoutId);
this.acknowledgementTimeouts.delete(messageId);
}
}
// 设置消息确认监听器
setupAcknowledgementListener() {
// 监听确认消息
this.on('message', (event) => {
try {
const message = JSON.parse(event.data);
if (message.type === 'acknowledgement') {
this.handleAcknowledgement(message);
} else if (message.type === 'data' && message.reliable) {
// 收到可靠消息,发送确认
this.sendAcknowledgement(message.messageId, message.sequenceNumber);
}
} catch (error) {
// 不是JSON消息,忽略
}
});
// 监听连接打开事件,重新发送未确认的消息
this.on('open', () => {
this.resendUnacknowledgedMessages();
});
}
// 处理确认消息
handleAcknowledgement(ackMessage) {
const { messageId, sequenceNumber } = ackMessage;
// 清理确认超时
this.clearAcknowledgementTimeout(messageId);
// 更新最后确认的序列号
if (sequenceNumber > this.lastAcknowledgedSequence) {
this.lastAcknowledgedSequence = sequenceNumber;
}
// 从待处理消息中移除
const message = this.pendingMessages.get(messageId);
if (message) {
message.status = 'acknowledged';
message.acknowledgedAt = Date.now();
this.pendingMessages.delete(messageId);
}
// 添加到已确认消息集合
this.acknowledgedMessages.add(messageId);
// 触发确认事件
this.emit('acknowledgement', {
messageId,
sequenceNumber,
timestamp: Date.now(),
message
});
// 清理过期的已确认消息
this.cleanupAcknowledgedMessages();
}
// 发送确认消息
sendAcknowledgement(messageId, sequenceNumber) {
const ackMessage = {
type: 'acknowledgement',
messageId,
sequenceNumber,
timestamp: Date.now()
};
this.sendJson(ackMessage).catch(error => {
console.warn('Failed to send acknowledgement:', error);
});
}
// 重新发送未确认的消息
resendUnacknowledgedMessages() {
const now = Date.now();
const unacknowledged = [];
// 收集未确认的消息
this.pendingMessages.forEach((message, messageId) => {
// 检查消息是否过期
if (now - message.timestamp > this.options.messageExpiry) {
console.log(`Message ${messageId} expired, removing`);
this.pendingMessages.delete(messageId);
this.clearAcknowledgementTimeout(messageId);
return;
}
// 检查消息是否需要重发
if (message.status === 'sent' || message.status === 'pending') {
unacknowledged.push(message);
}
});
// 重新发送未确认的消息
unacknowledged.forEach(message => {
console.log(`Resending unacknowledged message ${message.id}`);
const wrappedMessage = {
type: 'data',
reliable: true,
messageId: message.id,
sequenceNumber: message.seq,
data: message.data,
resent: true
};
this.sendJson(wrappedMessage).catch(error => {
console.warn(`Failed to resend message ${message.id}:`, error);
});
});
}
// 清理已确认的消息
cleanupAcknowledgedMessages() {
const maxSize = 1000; // 最多保留1000个已确认消息
if (this.acknowledgedMessages.size > maxSize) {
// 转换为数组,排序,删除最旧的
const sorted = Array.from(this.acknowledgedMessages)
.slice(-maxSize);
this.acknowledgedMessages = new Set(sorted);
}
}
// 覆盖父类的send方法,添加可靠性支持
send(data, options = {}) {
const { reliable = false, ...sendOptions } = options;
if (reliable) {
return this.sendReliable(data, sendOptions);
}
return super.send(data, sendOptions);
}
// 覆盖父类的sendJson方法
sendJson(data, options = {}) {
const { reliable = false, ...sendOptions } = options;
if (reliable) {
return this.sendReliable(data, sendOptions);
}
return super.sendJson(data, sendOptions);
}
// 获取可靠消息统计
getReliabilityStats() {
const now = Date.now();
let pendingCount = 0;
let acknowledgedCount = 0;
let failedCount = 0;
let expiredCount = 0;
this.pendingMessages.forEach(message => {
if (now - message.timestamp > this.options.messageExpiry) {
expiredCount++;
} else if (message.status === 'acknowledged') {
acknowledgedCount++;
} else if (message.status === 'failed') {
failedCount++;
} else {
pendingCount++;
}
});
return {
pendingMessages: pendingCount,
acknowledgedMessages: this.acknowledgedMessages.size,
failedMessages: failedCount,
expiredMessages: expiredCount,
totalSentMessages: this.messageSequence,
lastAcknowledgedSequence: this.lastAcknowledgedSequence,
messageQueueSize: this.messageQueue.length,
acknowledgementTimeouts: this.acknowledgementTimeouts.size
};
}
// 清理所有待处理消息
cleanupAllMessages() {
this.pendingMessages.clear();
this.acknowledgedMessages.clear();
this.messageQueue = [];
// 清理所有超时定时器
this.acknowledgementTimeouts.forEach(timeoutId => {
clearTimeout(timeoutId);
});
this.acknowledgementTimeouts.clear();
}
}
// 使用示例
class ReliableWebSocketExample {
static demonstrate() {
// 创建可靠WebSocket客户端
const reliableWs = new ReliableWebSocket('wss://echo.websocket.org', {
requireAcknowledgement: true,
acknowledgementTimeout: 3000,
messageExpiry: 60000, // 1分钟
reconnect: true
});
// 监听确认事件
reliableWs.on('acknowledgement', (ack) => {
console.log('Message acknowledged:', ack.messageId);
});
reliableWs.on('acknowledgement_timeout', (timeout) => {
console.warn('Acknowledgement timeout:', timeout.messageId);
});
// 发送可靠消息
reliableWs.sendReliable({ type: 'test', data: 'Hello reliable WebSocket!' })
.then(result => {
console.log('Reliable message sent and acknowledged:', result);
})
.catch(error => {
console.error('Failed to send reliable message:', error);
});
// 获取可靠性统计
const stats = reliableWs.getReliabilityStats();
console.log('Reliability stats:', stats);
return reliableWs;
}
}