长轮询需具备韧性:用AbortController设超时、指数退避重试、监听页面可见性、服务端返回带seq校验的标准化JSON、连续异常时降级至SSE或短轮询,并做好资源清理。

用 fetch 实现长轮询本身不难,但真正考验工程能力的是它出问题时怎么“不崩”——比如网络抖动、服务端挂起失败、代理中断、用户切页、浏览器休眠等。平滑降级不是加个 try-catch 就完事,而是让整个长轮询链路具备韧性:能感知异常、有退避策略、可恢复上下文、必要时自动切换备用通道。
客户端请求层必须控制超时与重试节奏
浏览器 fetch 默认无超时,而长轮询依赖服务端挂起响应,一旦后端未按预期返回(如被 Nginx 强制断连、PHP 脚本意外退出),fetch 会无限等待,导致页面卡死或内存泄漏。
- 务必显式设置 timeout:用
AbortController控制请求生命周期,超时时间应略大于服务端最大等待时间(例如服务端设了 25 秒超时,前端 timeout 设为 30000ms) - 错误重试不能固定间隔:首次失败后延迟 1s 重试,连续失败 3 次后指数退避(1s → 2s → 4s → 8s),避免雪崩式重连
- 监听页面可见性:当
document.hidden === true(用户切到其他标签页或锁屏),暂停发起新请求;回到前台时检查是否已断连,再决定是续连还是冷启动
服务端响应需携带状态标识与数据完整性校验
客户端无法区分“空响应”是服务端没数据、连接被截断,还是 JSON 解析失败。降级的前提是能准确归因。
- 服务端始终返回标准结构,例如:
{ "code": 0, "data": {...}, "seq": 12345, "ts": 1747998183 };code !== 0表示非业务异常(如超时、内部错误),客户端不应解析 data - 每次响应必须带唯一递增的 seq 序号,客户端缓存上一次成功接收的 seq;重连时可在请求头带上
X-Last-Seq: 12345,服务端据此判断是否漏推 - 禁止在响应体前/后输出任何额外字符(包括 BOM、空格、调试 echo),Content-Type 必须为
application/json; charset=utf-8
内置 fallback 机制:检测失效时自动切换通信方式
长轮询不是银弹。当连续多次超时或解析失败,说明当前通道不可靠,应主动降级,而非死磕。
- 统计最近 5 次请求:若超时率 ≥ 60% 或 JSON 解析失败 ≥ 2 次,触发降级开关
- 首选 fallback 是 SSE(Server-Sent Events):它原生支持自动重连、事件类型区分、last-event-id 断点续传,且服务端实现比长轮询更轻量(无需手动管理连接生命周期)
- 若 SSE 不可用(如旧版 IE),回落到带节流的短轮询(例如 30s 间隔),同时在 UI 显示“消息可能延迟”,降低用户预期
- 所有降级动作需记录日志(含时间、错误码、当前通道类型),便于后续分析根因
资源清理与连接复用要兼顾稳定性与内存安全
频繁创建/销毁 fetch 请求看似简单,但忽略 abort 和引用残留,容易引发内存泄漏或竞态更新。
- 每次 fetch 前生成独立
AbortController,组件卸载或用户登出时调用abort(),确保 pending 请求被中止 - 避免递归调用 longPolling() 导致调用栈过深;改用循环 +
while (active) { await doRequest(); }结构,配合await new Promise(r => setTimeout(r, delay))控制间隔 - 服务端返回空数据(如
{"code":0,"data":null})时,不视为错误,但需记录“空轮询次数”,超过阈值(如连续 10 次)主动触发降级检查

















