navigator.online不能单独用于逻辑熔断,必须配合心跳检测;它仅反映系统网络接口状态,路由器断网、dns故障等真实离线场景仍返回true,易导致误判漏判。

navigator.onLine 不能单独用于逻辑熔断,必须配合心跳检测才能避免误判和漏判。 它只反映操作系统网络接口状态,比如 Wi-Fi 关闭会立刻变 false,但路由器断网、DNS 故障、代理失效时仍返回 true——此时用户“看着在线,实际发不出请求”,直接靠它熔断会放行大量失败操作。
为什么 navigator.onLine === false 时不能直接跳过所有网络请求
它触发时机有限:仅在系统级网络切换(拔网线、开飞行模式)时变化,而真实业务中断常发生在连接建立后(如 TLS 握手失败、HTTP 502、超时)。若仅据此拦截 fetch 或 XMLHttpRequest,会导致:
- POST 表单提交被无故阻断,用户刷新后数据丢失
- WebSocket 连接尝试被跳过,但其实服务端还活着
- CDN 资源加载失败却没提示,页面部分功能静默降级
真正该熔断的是「已知不可达」的场景,比如连续三次心跳失败 + navigator.onLine === false,才可认为进入稳定离线态。
online / offline 事件监听必须防抖且手动校验初始状态
这两个事件不是实时的:Safari 在 iOS 后台切回前台可能不触发 online;Chrome 在快速开关 WiFi 时可能连发多次 offline。直接响应会导致 UI 频闪、重放队列混乱。
- 页面加载后立即执行
if (!navigator.onLine) { handleOffline() },补全初始状态判断 - 对事件回调加 500ms 防抖,相同状态变更在窗口期内只触发一次
- 用
addEventListener绑定,别用window.ononline = handler,否则无法统一移除 - 在 SPA 路由切换或组件卸载前调用
removeEventListener,防止内存泄漏
哪些操作该熔断、哪些该缓存、哪些必须强校验
熔断粒度要按副作用和幂等性区分,不能一刀切:
- GET 类读操作(如列表、详情页):可直接跳过,显示“暂无网络”,恢复后自动重试
- POST/PUT/DELETE 写操作:绝不能丢弃,应进 localStorage 队列(含时间戳、URL、body、method),重连后按序重放;需用接口签名去重,避免重复提交
-
登录、支付等关键流程:即使
navigator.onLine === true,也必须前置发起一次心跳(fetch('/api/ping', { signal: AbortSignal.timeout(3000) })),成功才允许下一步
服务端永远要做最终校验——前端熔断只是体验优化,不是安全边界。
自定义 network-status-change 事件比直接操作 DOM 更可控
把状态变更封装成语义化事件,能解耦 UI 更新与业务逻辑:
- 事件名用
network-status-change,不用online,避免和原生事件冲突 -
detail.isOnline传布尔值,方便下游用switch (e.detail.isOnline)判断 - 在事件回调里只派发事件,不直接修改 Vuex/Pinia state 或操作 DOM
- 若项目用状态管理库,让 store 订阅该事件并决定是否更新
isOnline字段
真正容易被忽略的是:心跳失败后不要立刻隐藏“重新连接中…”提示——得等下一次心跳成功再收起,否则网络抖动时文案会疯狂闪动。










