navigator.online仅反映操作系统网络接口状态,无法识别dns失败、代理中断等真实离线场景;必须结合online/offline事件监听与fetch探测(如/health接口)验证业务可达性。

navigator.onLine 的真实行为和常见误判
navigator.onLine 并不检测真实网络连通性,只反映浏览器是否认为自己“在线”——它依赖操作系统网络接口状态(如 Windows 的 NLM、macOS 的 SCNetworkReachability),对 Wi-Fi 已连接但 DNS 失败、代理中断、内网断外网等情况完全无感。用户常遇到「明明打不开 API,navigator.onLine 却返回 true」,就是这个原因。
实操建议:
- 永远别单独依赖
navigator.onLine做业务判断,它只适合快速过滤「明显离线」场景(比如拔网线、关 Wi-Fi) - 监听
online和offline全局事件时,注意它们**不会冒泡**,必须绑定在window上,且触发有延迟(通常 1–3 秒) - 移动端尤其要注意:iOS Safari 在后台标签页中可能冻结事件监听,
online事件可能延迟到切回前台才触发
用 fetch + 超时兜底做轻量级网络嗅探
真正的网络可用性得靠主动探测。用 fetch 请求一个稳定、低开销的 endpoint(如 /health 或 https://www.google.com/generate_204)最直接。关键不是“测通”,而是“测不通时能快速失败”。
实操建议:
- 避免轮询:用节流(throttle)控制探测频率,比如每 5 秒最多一次;首次页面加载后立即探测一次
- 必须设
timeout:原生fetch没 timeout 参数,需用AbortController配合Promise.race - 选探测地址要谨慎:
/favicon.ico可能被 CDN 缓存或重定向;推荐用同域空响应接口(HTTP 204),避免跨域和 CORS 干扰 - 错误处理重点捕获
TypeError(网络中断)、AbortError(超时),而非 HTTP 状态码 —— 404/500 不代表离线
const controller = new AbortController();
setTimeout(() => controller.abort(), 3000);
fetch('/health', { signal: controller.signal })
.then(r => r.ok)
.catch(err => err.name === 'TypeError' || err.name === 'AbortError');
结合状态机管理离线/恢复提示逻辑
直接弹 Toast 或覆盖层容易失控:网络抖动时频繁闪现、恢复时提示残留、用户正在输入表单却被打断。需要用简单状态机收敛行为。
实操建议:
- 定义三个状态:
online、checking、offline;仅当从checking→offline才显示「断网提示」,避免误触发 - 恢复提示(如「已重新连接」)只在
offline→online时出现,且加 1 秒防抖,防止短暂波动反复提示 - 所有 UI 提示必须可手动关闭,并记录用户关闭动作 —— 若用户点了「稍后提醒」,下次探测失败就别再弹
- 对表单页等敏感场景,检测到
offline后应禁用提交按钮,同时保留草稿(localStorage 或beforeunload提醒)
service worker 不是必需,但能补关键短板
navigator.onLine 和前端 fetch 探测都解决不了「页面已加载完成,但后续请求全挂了」的问题。比如用户打开页面时在线,5 分钟后断网,此时所有 API 调用静默失败。
实操建议:
- service worker 的
fetch事件能拦截所有网络请求,配合event.respondWith()可统一返回缓存或 fallback 响应,比每个 API 单独 try/catch 更可靠 - 无需完整 PWA:哪怕只注册一个最小 SW(几十行),拦截失败请求并 postMessage 给页面,就能让主逻辑感知「这次请求真失败了」
- 注意兼容性:Safari 对 SW 的支持仍有限(尤其 iOS),需降级为页面内 fetch 尝试 + 错误聚合上报
- 别在 SW 里做复杂状态同步 —— 它可能被系统终止,只做「拦截→响应→通知」三件事最稳
真正难的不是写几行代码,而是把「网络不可靠」当成常态来设计交互:提示位置是否遮挡操作?恢复后要不要自动重发未完成请求?离线时能否降级使用 IndexedDB 数据?这些细节比技术选型更消耗精力。










