SSE库选型+fetch-event-source示例
SSESSEServer-Sent Events是一种基于 HTTP 的单向推送技术允许服务端在一个长连接中持续向客户端发送事件。与 WebSocket 的双向通信不同SSE 更适合一些只需要从服务器获取数据的场景比如实时新闻更新、股票行情、通知系统等。针对项目实际需求需携带 Authorization 头、支持精细错误处理评估了三种主流方案。方案对比原生浏览器 EventSource API 使用简单但在鉴权、错误处理及非浏览器环境支持上存在明显限制。特性原生EventSourceevent-source-polyfillmicrosoft/fetch-event-source推荐实现方式浏览器原生 API基于 XMLHttpRequest 模拟 EventSource 行为基于 fetch 实现请求方法仅GEThttp长连接通常模拟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/EventSourcehttps://github.com/Azure/fetch-event-source综上更推荐使用 microsoft/fetch-event-source基于 fetch 实现请求控制更灵活可携带Authorization等自定义请求头。可在onopen、onerror、onclose 对连接状态和异常进行更清晰的调试与处理识别连接中断等问题。支持AbortController便于业务侧结合页面生命周期主动中断连接。相比原生 EventSource 和 event-source-polyfill它更符合“鉴权能力、连接控制、错误可观测性”的要求fetch-event-source实践代码这是一个订阅消息通知的接口示例展示从浏览器调试面板可以看到SSE 服务端返回的并不是一次性响应而是持续推送的事件流。其中CONNECT表示连接建立成功UNREAD_COUNT表示一条具体业务消息消息体为{message:0}。前端可以在onmessage中根据事件类型分别处理连接状态和业务数据这也是 SSE 特别适合通知、状态流等场景的原因。import{fetchEventSource,EventSourceMessage}frommicrosoft/fetch-event-sourceclassFatalErrorextendsError{}classRetriableErrorextendsError{}interfaceNoticeSubscribeOptions{token:string appId?:string onConnected?:()voidonUnreadCount?:(count:number)voidonFatalError?:(message:string)void}exportfunctionsubscribeNoticeSSE(url:string,options:NoticeSubscribeOptions,){constcontrollernewAbortController()consttaskfetchEventSource(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}:{}),},asynconopen(response){if(response.status401||response.status403){thrownewFatalError(SSE 鉴权失败)}if(!response.ok){thrownewRetriableError(SSE 连接失败HTTP${response.status})}constcontentTyperesponse.headers.get(content-type)||if(!contentType.includes(text/event-stream)){thrownewFatalError(接口返回的不是 SSE${contentType})}},onmessage(event:EventSourceMessage){switch(event.event){caseCONNECT:options.onConnected?.()returncaseUNREAD_COUNT:{try{constpayloadJSON.parse(event.data)as{message?:number|string}options.onUnreadCount?.(Number(payload.message??0))}catch{thrownewFatalError(UNREAD_COUNT 消息格式错误)}return}default:console.debug([SSE] unknown event:,event.event,event.data)}},onclose(){// 如果业务上不期望服务端主动结束可以在这里抛错并交给 onerror 重试thrownewRetriableError(SSE 连接已关闭)},onerror(error){if(errorinstanceofFatalError){options.onFatalError?.(error.message)throwerror}console.warn([SSE retry],error)return3000},})task.catch((error){if(!controller.signal.aborted){console.error([SSE stopped],error)}})return()controller.abort()}