SSE库选型+fetch-event-source示例

SSE

SSE(Server-Sent Events) 是一种基于 HTTP 的单向推送技术,允许服务端在一个长连接中持续向客户端发送事件。

与 WebSocket 的双向通信不同,SSE 更适合一些只需要从服务器获取数据的场景,比如实时新闻更新、股票行情、通知系统等。

针对项目实际需求(需携带 Authorization 头、支持精细错误处理),评估了三种主流方案。

方案对比

原生浏览器 EventSource API 使用简单,但在鉴权、错误处理及非浏览器环境支持上存在明显限制。

特性 原生 EventSource event-source-polyfill @microsoft/fetch-event-source(推荐)
实现方式 浏览器原生 API 基于 XMLHttpRequest 模拟 EventSource 行为 基于 fetch 实现
请求方法 GET,http长连接 通常模拟 GET 基于 fetch,请求控制更灵活
自定义请求头 ❌ 不支持 ✅ 支持 ✅ 支持
鉴权方式 通常依赖 cookie 或 query 可传 Authorization 可传 Authorization
握手阶段校验 能力较弱 一般 可在 onopen 中校验状态码、content-type
错误处理 较弱 一般 细粒度:可区分 HTTP 错误、响应类型异常、流关闭等
连接控制 较弱 一般 支持 AbortController 便于主动断开
重试治理 不可控,依赖浏览器默认行为 一般 可结合业务逻辑自定义重试策略
Node / SSR 适配 不友好(Node.js 无原生支持) 主要面向浏览器 更容易适配具备 fetch 能力的运行环境
页面可见性感知 集成 Page Visibility API 页面隐藏自动断连,可见时恢复(可配置关闭)
适用场景 简单 SSE 兼容场景、旧方案改造 现代前端项目、鉴权复杂场景
Github stars - 2.1k ⭐ 2.8k ⭐
github地址 - https://github.com/Yaffle/EventSource https://github.com/Azure/fetch-event-source

综上,更推荐使用 @microsoft/fetch-event-source

  1. 基于 fetch 实现,请求控制更灵活,可携带 Authorization 等自定义请求头。
  2. 可在 onopen、onerror、onclose 对连接状态和异常进行更清晰的调试与处理,识别连接中断等问题。
  3. 支持 AbortController,便于业务侧结合页面生命周期主动中断连接。
    相比原生 EventSource 和 event-source-polyfill,它更符合"鉴权能力、连接控制、错误可观测性"的要求

fetch-event-source实践代码

这是一个订阅消息通知的接口示例展示👆

从浏览器调试面板可以看到,SSE 服务端返回的并不是一次性响应,而是持续推送的事件流。

其中,CONNECT 表示连接建立成功,UNREAD_COUNT 表示一条具体业务消息,消息体为 {"message":0}

前端可以在 onmessage 中根据事件类型分别处理连接状态和业务数据,这也是 SSE 特别适合通知、状态流等场景的原因。

javascript 复制代码
import { fetchEventSource, EventSourceMessage } from '@microsoft/fetch-event-source'

class FatalError extends Error {}
class RetriableError extends Error {}

interface NoticeSubscribeOptions {
  token: string
  appId?: string
  onConnected?: () => void
  onUnreadCount?: (count: number) => void
  onFatalError?: (message: string) => void
}

export function subscribeNoticeSSE(
  url: string,
  options: NoticeSubscribeOptions,
) {
  const controller = new AbortController()

  const task = fetchEventSource(url, {
    method: 'GET',
    signal: controller.signal,
    openWhenHidden: true,
    headers: {
      Accept: 'text/event-stream',
      Authorization: options.token.startsWith('Bearer ')
        ? options.token
        : `Bearer ${options.token}`,
      ...(options.appId ? { appId: options.appId } : {}),
    },

    async onopen(response) {
      if (response.status === 401 || response.status === 403) {
        throw new FatalError('SSE 鉴权失败')
      }

      if (!response.ok) {
        throw new RetriableError(`SSE 连接失败,HTTP ${response.status}`)
      }

      const contentType = response.headers.get('content-type') || ''
      if (!contentType.includes('text/event-stream')) {
        throw new FatalError(`接口返回的不是 SSE:${contentType}`)
      }
    },

    onmessage(event: EventSourceMessage) {
      switch (event.event) {
        case 'CONNECT':
          options.onConnected?.()
          return

        case 'UNREAD_COUNT': {
          try {
            const payload = JSON.parse(event.data) as { message?: number | string }
            options.onUnreadCount?.(Number(payload.message ?? 0))
          } catch {
            throw new FatalError('UNREAD_COUNT 消息格式错误')
          }
          return
        }

        default:
          console.debug('[SSE] unknown event:', event.event, event.data)
      }
    },

    onclose() {
      // 如果业务上不期望服务端主动结束,可以在这里抛错并交给 onerror 重试
      throw new RetriableError('SSE 连接已关闭')
    },

    onerror(error) {
      if (error instanceof FatalError) {
        options.onFatalError?.(error.message)
        throw error
      }

      console.warn('[SSE retry]', error)
      return 3000
    },
  })

  task.catch((error) => {
    if (!controller.signal.aborted) {
      console.error('[SSE stopped]', error)
    }
  })

  return () => controller.abort()
}
相关推荐
Never_every991 小时前
8 个高清 4K 视频素材网址!无水印可商用
大数据·前端·音视频·视频
NotFound4861 小时前
分享实战中Python Web 框架对比:Django vs Flask vs FastAPI
前端·python·django
冰暮流星1 小时前
javascript之表单事件1
开发语言·前端·javascript
Dalydai2 小时前
AI 辅助前端开发:两个月踩坑实录
前端·ai编程
前端那点事2 小时前
Vue跨页面通信(8种主流方式|完整可运行Demo,Vue2/Vue3通用)
前端·vue.js
a_Ichuan2 小时前
在HBuilderX创建的uniapp项目中使用unocss
前端·uni-app
里欧跑得慢2 小时前
12. CSS滤镜效果详解:为页面注入艺术灵魂
前端·css·flutter·web
里欧跑得慢2 小时前
CSS 级联层:控制样式优先级的新方式
前端·css·flutter·web
前端那点事2 小时前
Vue大文件上传实现方案(企业级完整版)
前端·vue.js