
浏览器原生不提供 window.requestsOpen 这类全局网络请求计数属性或 networkCallOpen/Closed 标准事件,需通过封装 fetch 并结合自定义事件与请求计数器实现准确的加载状态控制。
浏览器原生不提供 `window.requestsopen` 这类全局网络请求计数属性或 `networkcallopen/closed` 标准事件,需通过封装 `fetch` 并结合自定义事件与请求计数器实现准确的加载状态控制。
在前端开发中,为提升用户体验,常需在页面显示“加载中”图标(如旋转 spinner)来反馈网络请求状态。但直接在每个 fetch 前后手动调用 showNetworkIcon() 和 hideNetworkIcon() 存在明显缺陷:并发请求下,后完成的请求可能被先完成的请求错误地关闭图标,导致视觉状态与实际网络活动不一致。
根本原因在于——浏览器 window 或 document 对象并未暴露任何原生属性(如 window.requestsOpen)或标准事件(如 "networkCallOpen")来反映当前活跃的网络请求数量或生命周期。虽然 Service Worker 中可通过 FetchEvent 拦截请求,但在主页面上下文中,唯一可原生利用的底层机制是 AbortSignal(用于中止请求),而非状态监听。
✅ 正确解法:封装 fetch,维护全局请求计数器,并触发语义化自定义事件。
以下是一个生产可用的封装示例,支持并发安全、URL 透传与错误兜底:
// 全局请求计数器(确保跨模块一致性)
let activeRequestCount = 0;
// 封装 fetch,自动管理加载状态
function networkFetch(url, options = {}) {
// 1. 请求开始:计数器+1,派发 open 事件
activeRequestCount++;
if (activeRequestCount === 1) {
window.dispatchEvent(new CustomEvent('networkCallOpen', { detail: { url } }));
}
// 2. 执行请求
return fetch(url, options)
.then(response => {
// 3. 请求结束:计数器-1
activeRequestCount = Math.max(0, activeRequestCount - 1);
// 4. 仅当所有请求都完成时,才派发 closed 事件
if (activeRequestCount === 0) {
window.dispatchEvent(new CustomEvent('networkCallClosed', { detail: { url } }));
}
return response;
})
.catch(error => {
// 5. 异常情况同样需减计数并触发 closed(避免状态卡死)
activeRequestCount = Math.max(0, activeRequestCount - 1);
if (activeRequestCount === 0) {
window.dispatchEvent(new CustomEvent('networkCallClosed', { detail: { url, error } }));
}
throw error;
});
}
// 使用示例(完全透明,无需修改业务逻辑)
async function loadData() {
try {
const res = await networkFetch('/api/users');
return await res.json();
} catch (err) {
console.error('请求失败:', err);
}
}
? 关键设计说明:
- ✅ 计数器驱动:
activeRequestCount是核心状态,保证多请求并发时图标只在 最后一个 请求结束时隐藏; - ✅ 事件语义清晰:
networkCallOpen在首个请求发起时触发(非每次);networkCallClosed在 全部请求完成 时触发(即“all calls closed”); - ✅ 异常安全:
catch中同步降级计数器,防止因未捕获错误导致加载图标永久显示; - ✅ 零侵入集成:业务代码只需将
fetch替换为networkFetch,其余逻辑(await、.json()等)保持不变。
⚠️ 注意事项:
- 避免在多个独立模块中重复定义
activeRequestCount—— 应作为单例状态(推荐通过 ES Module 导出或使用window.__networkCounter全局暂存); - 若项目已使用 Axios 等库,应在其
interceptors.request和interceptors.response中实现相同计数逻辑; - 不要依赖
XMLHttpRequest的onloadstart/onloadend,因其不覆盖fetch,且事件粒度粗(无法区分成功/失败)。
通过此封装,你获得了一个轻量、可靠、符合直觉的网络状态管理方案——它不依赖浏览器未实现的 API,而是用扎实的 JavaScript 状态机填补了这一空白。










