必须调用 event.preventDefault() 拦截表单默认提交,否则 fetch、localStorage 等逻辑全部失效;真实断网信号来自 fetch 抛出的 TypeError,而非 navigator.onLine。

submit 事件里必须 preventDefault() 才能接管重试
原生表单提交会立刻跳转或刷新页面,所有 JS 中断。不调用 event.preventDefault(),后续的 fetch、localStorage 存储、错误捕获全无效。
- 常见错误:只监听
submit但没阻止默认行为,用户看到页面闪一下就没了,实际请求可能发了一半就被终止 - 移动端尤其明显——键盘弹起/收起、屏幕旋转都可能触发意外 submit,更需第一时间锁住流程
- 禁用按钮只是辅助(
button.disabled = true),核心防线仍是preventDefault()
fetch 重试不能只靠 navigator.onLine 判断断网
navigator.onLine 在 WiFi 连着但路由器断外网、DNS 故障、代理失效时仍返回 true,完全不可信。真实断网信号来自 fetch() 抛出的 TypeError: Failed to fetch。
- 提交前建议发一次轻量探测(如
fetch('/api/ping', { method: 'HEAD', signal: AbortSignal.timeout(3000) })),失败才走离线路径 - 主提交请求也必须带
AbortSignal.timeout()(Safari 16.4+ 支持;旧版需手写AbortController+setTimeout) - 仅对
TypeError和HTTP 5xx重试,4xx(如 400、401)说明是客户端问题,重试无意义
重试要指数退避,且最大次数必须硬限制
移动端网络抖动频繁,连续重试会快速耗尽连接池、触发浏览器限流,甚至让用户误以为卡死。
- 推荐策略:第 1 次延迟 1s,第 2 次 2s,第 3 次 4s(即
Math.pow(2, retryCount)× 1000) - 最大重试次数设为 3,超限后不再自动重试,改为提示“网络不稳定,请稍后手动重试”
- 每次重试前检查
document.hidden,页面在后台时暂停重试,避免锁屏后还在后台狂发请求
离线暂存必须用 localStorage + 时间戳键名,别碰 sessionStorage
用户切到微信、接电话、锁屏再回来,sessionStorage 已清空。真正可用的是 localStorage,但得规避覆盖风险。
立即学习“前端免费学习笔记(深入)”;
- 存的时候用带时间戳的键名:
localStorage.setItem('pending_form_' + Date.now(), JSON.stringify(data)) - 含文件的表单只能用
FormData,不能转成纯对象再JSON.stringify—— 文件 Blob 会丢失 - 重发成功后必须立即
localStorage.removeItem(key),否则下次打开页面又触发重复提交
preventDefault() 拦截后的异步流程里,二是真正的断网信号永远来自 fetch 异常,不是 navigator.onLine。其他都是围绕这两条展开的防御性设计。



















