SuperAgent 浏览器端网络状态检测:离线时的请求队列处理
在现代 Web 应用开发中,用户对网络稳定性的依赖与日俱增。然而,网络连接的不稳定性(如地铁、弱信号区域等场景的信号中断)常导致请求失败,影响用户体验。SuperAgent 作为一款轻量级的 JavaScript HTTP 客户端(HTTP Client),虽未内置完整的离线请求队列功能,但通过其灵活的 API 和事件机制,可构建可靠的离线请求处理系统。本文将详细介绍如何基于 SuperAgent 实现浏览器端网络状态检测、请求队列管理及离线数据同步,解决网络波动带来的数据一致性问题。
网络状态检测机制
浏览器提供了原生的网络状态 API,可实时监测网络连接变化。结合 SuperAgent 的错误处理机制,能精准识别离线场景并触发相应处理逻辑。
浏览器网络 API 应用
使用 navigator.onLine 属性和 online/offline 事件可实现基础网络状态监听:
// 初始网络状态检查
const isOnline = () => navigator.onLine;
// 网络状态变化监听
window.addEventListener('online', () => {
console.log('网络已恢复,同步离线请求队列');
syncOfflineQueue();
});
window.addEventListener('offline', () => {
console.log('网络已断开,启用离线请求队列');
});
SuperAgent 错误类型识别
SuperAgent 在网络错误时会抛出特定异常,可通过错误信息判断是否为离线状态。在 src/client.js 中定义了跨域错误处理逻辑:
// 源自 SuperAgent 源码:src/client.js 第 655 行
Request.prototype.crossDomainError = function () {
const error = new Error(
'Request has been terminated\nPossible causes: the network is offline, Origin is not allowed by Access-Control-Allow-Origin, the page is being unloaded, etc.'
);
error.crossDomain = true;
this.callback(error);
};
通过错误信息中的 "network is offline" 关键字,可识别离线场景:
request.get('/api/data')
.end((err, res) => {
if (err && err.message.includes('network is offline')) {
// 处理离线错误
queueRequest({
method: 'GET',
url: '/api/data',
callback: (res) => console.log('恢复后的数据:', res)
});
}
});
请求队列核心设计
离线请求队列需解决三个关键问题:请求存储、优先级管理和恢复策略。采用 IndexedDB 进行持久化存储,结合队列数据结构实现高效管理。
队列数据结构设计
class RequestQueue {
constructor() {
this.queue = [];
this.priorityMap = { 'GET': 1, 'POST': 2, 'PUT': 3, 'DELETE': 4 };
}
// 添加请求到队列,按方法优先级排序
enqueue(request) {
request.timestamp = Date.now();
this.queue.push(request);
this.queue.sort((a, b) => {
// 优先级高的请求先执行
if (this.priorityMap[a.method] !== this.priorityMap[b.method]) {
return this.priorityMap[b.method] - this.priorityMap[a.method];
}
// 相同优先级按时间排序
return a.timestamp - b.timestamp;
});
this.persistQueue(); // 持久化到 IndexedDB
}
// 获取下一个待执行请求
dequeue() {
const request = this.queue.shift();
this.persistQueue();
return request;
}
// 清空队列
clear() {
this.queue = [];
this.persistQueue();
}
// 持久化队列到 IndexedDB
persistQueue() {
// 实现 IndexedDB 存储逻辑
}
}
离线请求存储方案
使用 IndexedDB 而非 localStorage 存储请求,原因如下:
- 支持更大存储容量(通常为 50MB 以上)
- 提供事务支持,确保数据一致性
- 可存储复杂对象,无需手动序列化
// IndexedDB 存储实现示例
class OfflineStorage {
constructor() {
this.dbName = 'superagent-offline-queue';
this.storeName = 'requests';
}
// 打开数据库连接
open() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains(this.storeName)) {
db.createObjectStore(this.storeName, { keyPath: 'id', autoIncrement: true });
}
};
request.onsuccess = (event) => {
this.db = event.target.result;
resolve(this.db);
};
request.onerror = (event) => reject(event.target.error);
});
}
// 保存请求到数据库
saveRequest(request) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([this.storeName], 'readwrite');
const store = transaction.objectStore(this.storeName);
const request = store.add({
method: request.method,
url: request.url,
data: request.data,
timestamp: Date.now(),
retries: 0
});
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
}
SuperAgent 请求拦截与封装
通过封装 SuperAgent 的请求方法,实现请求自动入队、网络状态检测和错误恢复的无缝集成。
请求拦截器实现
// 增强版 SuperAgent 请求函数
const createOfflineRequest = (superagent, queue) => {
return async (method, url, data) => {
// 检查网络状态
if (!isOnline()) {
console.log(`网络离线,将 ${method} ${url} 加入队列`);
return new Promise((resolve) => {
queue.enqueue({
method,
url,
data,
resolve,
reject: (err) => console.error('请求失败:', err)
});
});
}
// 网络正常,直接发送请求
return new Promise((resolve, reject) => {
superagent[method.toLowerCase()](url)
.send(data)
.end((err, res) => {
if (err && isNetworkError(err)) {
// 网络错误,加入队列
queue.enqueue({ method, url, data, resolve, reject });
} else if (err) {
reject(err);
} else {
resolve(res);
}
});
});
};
};
// 网络错误判断函数
const isNetworkError = (err) => {
// 基于 SuperAgent 错误信息判断网络错误
return err.message.includes('network is offline') ||
err.message.includes('Failed to fetch') ||
err.status === 0; // 状态码 0 通常表示网络错误
};
完整请求流程控制
// 初始化组件
const initOfflineSystem = async () => {
const queue = new RequestQueue();
const storage = new OfflineStorage();
await storage.open();
// 从数据库加载历史队列
const storedRequests = await storage.getRequests();
storedRequests.forEach(req => queue.enqueue(req));
// 创建增强版请求函数
const request = createOfflineRequest(superagent, queue);
// 暴露全局 API
window.offlineRequest = request;
window.syncOfflineQueue = () => syncQueue(queue, storage, request);
return request;
};
// 同步离线队列
const syncQueue = async (queue, storage, request) => {
while (queue.length > 0 && isOnline()) {
const req = queue.dequeue();
try {
console.log(`同步离线请求: ${req.method} ${req.url}`);
const res = await request(req.method, req.url, req.data);
req.resolve(res);
await storage.deleteRequest(req.id); // 从数据库删除已完成请求
} catch (err) {
req.retries++;
if (req.retries < 3) {
queue.enqueue(req); // 重试次数未满,重新入队
} else {
req.reject(err); // 超过重试次数,触发错误回调
}
}
}
};
离线同步策略
离线请求恢复并非简单的按序重发,需考虑请求依赖、幂等性和数据一致性问题,制定合理的同步策略。
同步优先级规则
- 方法优先级:DELETE > PUT > POST > GET(确保数据修改操作优先同步)
- 时间戳排序:相同方法按请求发起时间排序
- 依赖处理:通过请求 ID 建立依赖关系,支持链式同步
// 带依赖关系的请求同步
const syncWithDependencies = async (queue) => {
const independentRequests = queue.filter(req => !req.dependsOn);
// 先处理无依赖的请求
for (const req of independentRequests) {
await executeRequest(req);
}
// 处理有依赖的请求
const dependentRequests = queue.filter(req => req.dependsOn);
for (const req of dependentRequests) {
const dependency = queue.find(r => r.id === req.dependsOn);
if (!dependency || dependency.status === 'completed') {
await executeRequest(req);
} else {
// 依赖未完成,放回队列尾部
queue.enqueue(req);
}
}
};
冲突解决机制
当离线状态下对同一资源进行多次修改,恢复网络后可能产生数据冲突。可采用以下策略解决:
- 版本号控制:为每个资源添加版本字段,服务端验证版本一致性
- 时间戳策略:以最后修改时间为准,覆盖旧数据
- 合并策略:对可合并的字段(如数组、计数器)进行增量合并
// 请求冲突处理示例
const handleRequestConflict = async (req, conflictError) => {
// 获取服务端最新数据
const serverData = await fetchLatestData(req.url);
// 根据冲突类型选择解决策略
if (conflictError.type === 'version_conflict') {
// 使用最新版本号重试请求
req.data.version = serverData.version;
return req;
} else if (conflictError.type === 'data_conflict') {
// 合并本地修改与服务端数据
const mergedData = mergeData(req.data, serverData);
req.data = mergedData;
return req;
}
// 无法自动解决,触发用户干预
throw new Error('需要用户手动解决数据冲突');
};
完整实现案例
整合上述组件,构建一个完整的离线请求处理系统,包含请求队列、网络监听、数据持久化和同步恢复功能。
系统架构图
核心代码集成
// 初始化离线请求系统
const init = async () => {
// 创建组件实例
const networkMonitor = new NetworkMonitor();
const storage = new OfflineStorage();
await storage.open();
const queue = new RequestQueue(storage);
await queue.restore(); // 从存储恢复队列
// 创建增强版 SuperAgent
const request = SuperAgentEnhancer.create(superagent, queue, networkMonitor);
// 创建同步管理器
const syncManager = new SyncManager(queue, storage, request);
// 绑定网络事件
networkMonitor.onOnline(() => syncManager.sync());
// 暴露全局 API
window.offlineRequest = request;
console.log('离线请求系统初始化完成');
return request;
};
// 应用示例
init().then(request => {
// 使用增强版请求函数
request('POST', '/api/tasks', { title: '离线任务' })
.then(res => console.log('任务创建成功:', res.body))
.catch(err => console.error('任务创建失败:', err));
});
性能优化建议
- 批量同步:网络恢复时,合并多个小请求为批量请求
- 请求压缩:对队列中的请求数据进行压缩存储
- 后台同步:使用 Service Worker 在页面关闭后继续同步
- 优先级调度:根据用户行为动态调整同步优先级
// 批量请求合并示例
const batchRequests = (requests) => {
// 按 URL 分组
const grouped = {};
requests.forEach(req => {
if (!grouped[req.url]) grouped[req.url] = [];
grouped[req.url].push(req);
});
// 生成批量请求
const batchRequests = [];
Object.keys(grouped).forEach(url => {
const requests = grouped[url];
if (requests.length > 1 && requests[0].method === 'POST') {
// 合并多个 POST 请求为批量操作
batchRequests.push({
method: 'POST',
url: `${url}/batch`,
data: { operations: requests.map(r => ({ id: r.id, data: r.data })) }
});
} else {
// 非批量请求直接添加
batchRequests.push(...requests);
}
});
return batchRequests;
};
总结与扩展
基于 SuperAgent 构建的离线请求处理系统,通过网络状态监测、请求队列管理和智能同步策略,有效解决了网络波动导致的数据一致性问题。该方案具有以下优势:
- 侵入性低:通过封装而非修改 SuperAgent 源码实现功能增强
- 可扩展性强:模块化设计支持自定义存储、同步和冲突解决策略
- 兼容性好:基于标准 Web API,支持主流浏览器
潜在扩展方向
- 与 Service Worker 集成:实现后台同步和推送通知
- 请求优先级动态调整:结合用户行为数据优化同步顺序
- 离线数据分析:统计离线时长、请求失败率等指标
- 用户可控同步:提供 UI 界面让用户手动触发和解决冲突
官方文档:README.md
通过本文介绍的方法,开发者可显著提升 Web 应用在弱网环境下的稳定性和用户体验,使应用具备接近原生应用的离线工作能力。SuperAgent 的灵活性使其成为构建此类系统的理想选择,而合理的架构设计则确保了系统的可靠性和可维护性。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



