随手记录的一些关于长轮询和SSE的内容

不使用setTimeout的长轮询

在前端实现长轮询时,可以不用 setTimeout,某些场景下,使用它可能是一种不好的实践。

1. 如何不用 setTimeout 实现长轮询?

长轮询的核心逻辑是:客户端发送请求 → 服务器保持连接直到有数据或超时 → 客户端收到响应后立即重新发起请求
递归调用 是一种无需 setTimeout 的直接实现方式:

javascript 复制代码
function longPoll() {
  fetch('/data')
    .then(response => response.json())
    .then(data => {
      // 处理数据
      console.log("Received data:", data);
      // 立即重新发起请求(递归调用)
      longPoll();
    })
    .catch(error => {
      // 处理错误(如网络中断)
      console.error("Fetch error:", error);
      // 可选:延迟重试(这里可能需要 `setTimeout`)
      setTimeout(longPoll, 1000); // 错误后重试间隔
    });
}

// 启动长轮询
longPoll();

关键点:

  • 递归调用 :在 fetch 成功或失败后,直接调用 longPoll(),形成循环。
  • 无需 setTimeout:请求完成或超时后立即重新发起,避免了固定间隔的延迟。
  • 错误处理 :失败时可以选择延迟重试(此时可能需要 setTimeout),但核心循环无需定时器。

2. 使用 setTimeout 是不是不好的实践?

不一定,但需要权衡:

使用 setTimeout 的问题:

  • 浏览器限制 :当页面处于后台或不活动时,浏览器会限制定时器的精度(如最小间隔为 1 秒或更长),导致请求间隔不准确,影响实时性。
  • 资源浪费 :若服务器响应很快,setTimeout 可能强制等待固定间隔(如 5 秒),导致客户端无法立即重新请求,延迟数据更新
  • 复杂性:需要手动管理定时器的启动、清理和错误重试逻辑。

递归调用的优势:

  • 即时重连 :响应结束后立即发起新请求,零延迟,实时性更高。
  • 更简洁:无需管理定时器 ID,代码更清晰。
  • 资源高效:仅在必要时发起请求,减少空闲时的资源占用。

何时可能需要 setTimeout

  • 错误重试策略:在网络错误时,可能需要延迟重试(如指数退避)。
  • 服务器超时配置:如果服务器的长轮询接口设置了最大等待时间(如 30 秒),客户端可能需要在超时后等待一段时间再重试(避免频繁请求)。

3. 完整的健壮实现(递归 + 错误处理)

javascript 复制代码
let isPolling = false; // 防止重复请求

function startLongPoll() {
  if (isPolling) return;
  isPolling = true;

  fetch('/data', { 
    timeout: 30000 // 可选:设置客户端超时(单位:毫秒)
  })
    .then(response => {
      if (!response.ok) {
        throw new Error(`HTTP error! Status: ${response.status}`);
      }
      return response.json();
    })
    .then(data => {
      // 处理数据
      console.log("Received data:", data);
      // 成功后立即重新开始
      startLongPoll();
    })
    .catch(error => {
      console.error("Polling error:", error);
      // 错误后延迟重试(使用 setTimeout)
      setTimeout(() => {
        startLongPoll();
      }, 1000); // 1秒后重试
    })
    .finally(() => {
      isPolling = false;
    });
}

// 启动长轮询
startLongPoll();

主要说明:

  1. 防重复标记 :通过 isPolling 避免并发请求。
  2. 客户端超时 :设置 timeout 属性,防止请求无限挂起。
  3. 错误重试 :失败后延迟重试(此处使用 setTimeout 是合理的)。
  4. 资源清理 :在 finally 中重置状态。

4. 总结

  • 不用 setTimeout 的实现 :通过递归调用 fetch 实现长轮询的循环,无需定时器。
  • setTimeout 的适用场景 :仅在错误重试服务器超时配置时需要,但核心循环无需依赖它。
  • 最佳实践:优先使用递归调用,结合防重复标记和错误重试策略,确保高效和健壮性。

如果需要更复杂的实时通信(如全双工),建议改用 WebSocketServer-Sent Events (SSE)

Server-Sent Events (SSE)是什么

Server-Sent Events (SSE) 是一种基于 HTTP 协议 的技术,允许 服务器主动向客户端(如浏览器)推送实时数据,而无需客户端频繁轮询。它是 HTML5 的一部分,提供了一种轻量级、单向的通信机制,特别适合需要实时更新的场景。


核心特点

  1. 单向通信

    • 数据仅从 服务器流向客户端,无法反向传输(如 WebSocket 是双向的)。
    • 若需双向通信,需结合其他技术(如 WebSocket)。
  2. 基于 HTTP 协议

    • 使用标准 HTTP 连接,无需额外协议握手。
    • 客户端通过 EventSource 接口发起请求,服务器通过 text/event-stream 格式响应。
  3. 自动重连机制

    • 若连接中断(如网络问题),浏览器会自动重新建立连接 ,并尝试从上次断开的位置继续接收数据(通过 Last-Event-ID 标识)。
  4. 事件流格式

    • 服务器以特定文本格式发送数据,每条消息包含:
      • 数据data:):实际内容。
      • 事件类型event:):可选,用于区分不同事件类型。
      • 事件 IDid:):用于重连时标识数据位置。
      • 注释: 开头):客户端忽略,用于调试。
  5. 低资源消耗

    • 相比 WebSocket,协议更简单,实现成本低,适合单向实时场景。

工作原理

  1. 建立连接

    • 客户端通过 EventSource 对象发起请求,指定 SSE 端点(如 /stream)。
    • 请求头包含 Accept: text/event-stream,表明期望接收事件流。
  2. 服务器响应

    • 服务器响应头设置:

      http 复制代码
      Content-Type: text/event-stream
      Cache-Control: no-cache
      Connection: keep-alive
    • 保持连接打开,持续发送数据流。

  3. 数据推送

    • 服务器按需发送消息,每条消息以 data: 开头,换行结束:

      css 复制代码
      data: { "message": "Hello, SSE!" }\n\n
    • 可选字段:

      • event: 指定事件类型(如 event: update)。

      • id: 标识消息序号(用于重连恢复)。

      • 注释以冒号 : 开头,客户端忽略:

        复制代码
        : keep-alive comment\n
  4. 客户端处理

    • 通过 EventSource 监听事件:

      javascript 复制代码
      const eventSource = new EventSource('/stream');
      eventSource.onmessage = (event) => {
        console.log('Received:', event.data);
      };
      eventSource.onerror = (error) => {
        console.error('SSE error:', error);
      };

典型使用场景

  1. 实时通知系统
    • 如社交媒体的点赞、评论提醒。
  2. 实时数据监控
    • 股票行情、服务器日志实时更新。
  3. 仪表盘数据流
    • 实时统计图表(如用户活跃度、订单量)。

与长轮询、WebSocket 的对比

特性 SSE 长轮询 WebSocket
通信方向 单向(服务器→客户端) 单向(服务器→客户端) 双向
协议 HTTP HTTP TCP 基础上的自定义协议
连接状态 长连接(保持打开) 短连接(每次请求后关闭) 长连接
自动重连 浏览器内置支持 需手动实现 需手动实现
复杂度 简单(基于 HTTP) 简单,但需管理请求间隔 复杂(需握手和协议处理)
适用场景 实时单向数据流 低频更新(如邮件通知) 高频双向通信(如游戏、聊天)

示例代码

服务器端(Node.js + Express)

javascript 复制代码
const express = require('express');
const app = express();

app.get('/stream', (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  // 每秒推送当前时间
  const interval = setInterval(() => {
    res.write(`data: ${new Date().toISOString()}\n\n`);
  }, 1000);

  // 连接关闭时清除定时器
  req.on('close', () => {
    clearInterval(interval);
    res.end();
  });
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

客户端(JavaScript)

javascript 复制代码
if (typeof EventSource !== 'undefined') {
  const eventSource = new EventSource('/stream');

  eventSource.onmessage = (event) => {
    console.log('New message:', event.data);
    // 更新 UI 或处理数据
  };

  eventSource.onerror = (error) => {
    console.error('SSE error:', error);
  };
} else {
  console.error('Browser does not support SSE');
}

优缺点

优点 缺点
基于 HTTP,兼容性好(除旧版 IE) 仅单向通信
自动重连,减少客户端开发复杂度 依赖 HTTP 连接,可能被防火墙/代理阻断
适合低频到中频的实时数据推送 不支持二进制数据,仅文本格式

适用性总结

  • 选择 SSE 的场景:需要服务器单向推送实时数据,且无需复杂双向交互(如通知、监控)。
  • 替代方案
    • WebSocket:需双向通信(如聊天、游戏)。
    • 长轮询:服务器负载较低,或需兼容极旧浏览器。
    • MQTT/WebRTC:特定物联网或高实时需求场景。
相关推荐
self-discipline6346 分钟前
【Java】Java核心知识点与相应面试技巧(七)——类与对象(二)
java·开发语言·面试
灵感__idea1 小时前
JavaScript高级程序设计(第5版):扎实的基本功是唯一捷径
前端·javascript·程序员
摇滚侠1 小时前
Vue3 其它API toRow和markRow
前端·javascript
難釋懷1 小时前
JavaScript基础-history 对象
开发语言·前端·javascript
beibeibeiooo1 小时前
【CSS3】04-标准流 + 浮动 + flex布局
前端·html·css3
拉不动的猪1 小时前
刷刷题47(react常规面试题2)
前端·javascript·面试
浪遏1 小时前
场景题:大文件上传 ?| 过总字节一面😱
前端·javascript·面试
Bigger2 小时前
Tauri(十八)——如何开发 Tauri 插件
前端·rust·app
355984268550552 小时前
医保服务平台 Webpack逆向
前端·webpack·node.js
百锦再3 小时前
React编程的核心概念:发布-订阅模型、背压与异步非阻塞
前端·javascript·react.js·前端框架·json·ecmascript·html5