当你盯着AI聊天界面,看着文字一个字一个字蹦出来时; 表面:哇,好智能! 内心OS:这玩意儿怎么不卡顿?请求不会超时吗?
欢迎来到SSE流式输出的世界 - 这里没有魔法,只有协议
一、SSE:不是WebSocket的表弟#
想象两个场景:
- 传统HTTP:打电话问朋友午饭吃什么,他说”我想想…(10秒)…吃拉面吧” - 全程静音等待
- SSE流式输出:同个问题,朋友实时碎碎念:“呃…火锅?…太贵…烤肉?…排队…算了…还是拉面!“
// 真实世界中的SSEconst sse = new EventSource('/api/food-suggestion');sse.onmessage = e => { // 输出流: // data: 正在思考... // data: 排除选项:火锅 // data: 最终决定:拉面(加蛋)};
二、sse方案对比(技术选型)#
1. EventSource:浏览器原生工具人#
- 优点:就像用js写console.log(“hello world”) - 简单到不好意思收费
- 缺点:只能GET请求,相当于去餐厅只能点”今日特价”,还不能提要求
适用场景:
// 监控服务器状态这类只读操作const serverMonitor = new EventSource('/api/server-status');serverMonitor.addEventListener('cpu', e => { console.log(`CPU负载:${e.data}%`); // 当数值>90时,GG 😱});
2. Fetch + ReadableStream:手工耿的焊枪#
- 优点:能发POST请求,自定义Header - 相当于给HTTP戴上了机械臂
- 缺点:要手动处理数据流,重连逻辑自己焊
代码实战:
// 给AI发送量子物理问题的正确姿势fetch('/api/quantum-physics', { method: 'POST', body: JSON.stringify({ question: "薛定谔的猫死了吗?" }), headers: { 'X-XX-XXX': 'true' }}).then(async res => { const reader = res.body.getReader(); const decoder = new TextDecoder();
while(true) { const { done, value } = await reader.read(); if(done) break;
// 逐字解析AI的哲学思考 const text = decoder.decode(value); console.log(text); // 输出:既...死...又...活... }});
最佳场景:需要提交复杂参数(比如让AI写3000字论文提纲)
3. fetch-event-source:自动步枪#
- 优点:封装了重连/中断/错误处理 - 相当于给Fetch套了防弹衣
- 缺点:需要npm install,增加1.2秒的决策成本
企业级用法:
import { fetchEventSource } from '@microsoft/fetch-event-source';
// 和AI讨论核聚变技术的安全方案await fetchEventSource('/api/nuclear-fusion', { method: 'POST', headers: { 'Authorization': 'Bearer top-secret' }, body: JSON.stringify({ clearanceLevel: 5 }), onmessage: (event) => { if(event.id === 'WARNING') { // AI说:建议先购买人身保险 console.warn(event.data); } }});
适用场景:需要Token验证的敏感业务(比如让AI计算年终奖…)
4. WebSocket伪装术:航母送外卖#
- 优点:复用现有WebSocket连接 - 相当于用航天飞机取快递
- 缺点:协议冗余度堪比在README写毕业论文
迷惑行为大赏:
const ws = new WebSocket('wss://api/ai-chat');ws.onmessage = (e) => { /* 实际传输的数据: {"type":"sse","data":"为什么用WebSocket模拟SSE?"} {"type":"sse","data":"因为PM说'反正都是流嘛'"} */};
使用建议:当CTO说”就用现有WebSocket吧”时的妥协方案
三、方案选型决策树#
四、SSE翻车现场实录#
案例1:忘记关闭连接
// 组件卸载时useEffect(() => { const sse = new EventSource('/api/chat');
// 忘记写清理函数的结果: // 用户离开页面后,AI还在后台自言自语 // 服务器日志:《编号89751》已持续连接18小时...}, []);
案例2:错误的内容类型
// 服务端错误响应HTTP/1.1 200 OKContent-Type: application/json // 应该是text/event-stream!
{"message": "说好的流式输出呢?..."}
案例3:代理服务器缓冲
location /chat { proxy_pass http://ai-backend; proxy_buffering on; // 默认开启的灾难 # 结果:数据攒到4KB才发送,用户看AI在"思考人生"}
五、终极建议#
选择SSE方案就像选外卖:
- EventSource:食堂套餐 - 不用想直接吃,但菜色固定不能换
- Fetch+Stream:自己下厨 - 想吃什么做什么,但得买菜洗碗很麻烦
- fetch-event-source:米其林外卖 - 贵但送得快还带餐具
- WebSocket方案:叫个跑腿坐专车去餐厅打包 - 能吃到但等得久还贵
最后提醒:
当AI回复”让我想想…”时
它可能不是真的在思考 😉
而是你的SSE配置出问题了