如何让AI打字机效果在浏览器中流畅运行?

摘要:后端实现了流式输出,前端却不知道怎么接?本文详细讲解三种主流的前端接收方式:原生EventSource、fetch流式读取、微软fetch-event-source库,并对比优缺点。同时总结了前端最容易翻车的五个大坑(CORS、数据格式、重
🚀 EventSource、fetch流式读取……总有一款适合你 嘿,老朋友!上次咱们把FastAPI后端改造成了“水龙头”,AI一个字一个字往外蹦。然后我偷偷懒,说“前端代码网上大把”,结果后台好多小伙伴留言:“后端搞定了,前端死活接不上,救救我!” 明明后端打印得好好的,浏览器就是没反应;要么收到一堆乱码,要么连接一会儿就断。这感觉就像水龙头打开了,管子却是堵的。今天咱们就把这根“管子”彻底疏通,顺便把那些隐藏的“水垢”全清掉!
📡 前端接流三大主流方式 后端返回的是text/event-stream,也就是 Server-Sent Events (SSE)。前端接收它主要有三种姿势: 🔹 方式1:浏览器原生EventSource —— 最简单,但功能有限 🔹 方式2:fetch API + 流式读取 —— 更灵活,可自定义请求头 🔹 方式3:第三方库 @microsoft/fetch-event-source —— 全能选手,自动重连 🌰 方式1:EventSource —— 杀鸡用牛刀?够用就行! 如果你不需要自定义请求头(比如不需要带Token),EventSource是最快的接入方式。几行代码搞定: const eventSource = new EventSource('http://localhost:8000/chat?prompt=你好'); eventSource.onmessage = (event) => { if (event.data === '[DONE]') { console.log('对话结束'); eventSource.close(); return; } // 把内容追加到页面 document.getElementById('output').innerText += event.data; }; eventSource.onerror = (err) => { console.error('连接出错', err); eventSource.close(); }; 注意:EventSource只能发送GET请求,且不能添加自定义Headers(比如Authorization)。 如果你需要POST携带复杂参数,就得用下面两种。 🌊 方式2:fetch + 流式读取 —— 真正的全栈式控制 fetch API 从 Chrome 95 开始完美支持流式响应。我们可以像读小说一样逐段读取数据: async function fetchStream() { const response = await fetch('http://localhost:8000/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: '讲个故事' }) }); const reader = response.body.getReader(); const decoder = new TextDecoder(); while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value); // 假设后端每块返回 "data: xxx\n\n" 格式,需要解析 const lines = chunk.split('\n'); for (const line of lines) { if (line.startsWith('data: ')) { const data = line.slice(6); if (data === '[DONE]') return; document.getElementById('output').innerText += data; } } } } fetchStream(); 这种方式自由度超高,可以加Token,可以POST,甚至可以处理二进制流。但要自己手动解析SSE格式,容易在换行符上踩坑。 🛡️ 方式3:@microsoft/fetch-event-source —— 躺平式接入 微软出品,专治各种SSE不服。它内置了断线重连、自动解析、错误恢复等功能,强烈推荐在生产环境使用: import { fetchEventSource } from '@microsoft/fetch-event-source'; await fetchEventSource('http://localhost:8000/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: '写首诗' }), onmessage(ev) { if (ev.data === '[DONE]') { console.log('完成'); return; } document.getElementById('output').innerText += ev.data; }, onerror(err) { console.error('大事不妙', err); }, onclose() { console.log('连接关闭'); } }); 它会自动处理SSE格式,只要关注onmessage里的ev.data即可。连[DONE]都得自己判断,它只管传数据。 ⚠️ 前方高能:前端最容易翻车的五个坑 💥 坑1:CORS 跨域 —— 万恶之源 后端就算配了CORS,如果前端用了withCredentials或自定义Header,依然可能触发预检请求(OPTIONS),后端必须处理。用fetch-event-source时,它默认不会发credentials,注意设置。 💥 坑2:数据格式必须严格遵循 SSE 规范 后端每块必须是 data: xxx\n\n,两个换行符不能少。否则EventSource和微软库都可能解析失败。fetch方式自己解析也要注意换行可能跨块。 💥 坑3:连接意外断开与重连机制 EventSource 默认会自动重连,但如果是HTTP 401/500等错误,它会一直重连直到地老天荒。最好监听onerror判断状态码,必要时关闭。微软库提供了onerror回调,可以控制是否终止重连。 💥 坑4:浏览器兼容性 EventSource 和 fetch 的流式读取在 IE 和部分旧手机浏览器上不可用。可以用微软库的 polyfill,或者提示用户升级浏览器。 💥 坑5:内存泄漏与连接未关闭 组件卸载或页面跳转时,一定要调用eventSource.close()或中止fetch的AbortController,否则连接会一直挂起,浪费资源。 🎯 进阶小贴士:让打字机体验更逼真 前端显示时,可以用定时器稍微打散一下文字出现节奏,模拟真人打字。但注意不要过度,否则用户会疯: let buffer = ''; onmessage(ev) { buffer += ev.data; // 每50ms渲染一个字符 if (!typingTimer) { typingTimer = setInterval(() => { if (buffer.length > 0) { output.innerText += buffer[0]; buffer = buffer.slice(1); } else { clearInterval(typingTimer); typingTimer = null; } }, 50); } } 另外,记得在UI上给个“停止生成”的按钮,调用abort()或close(),让用户随时打断。
好啦,前端三大流派和五个大坑都交代清楚了。你现在可以自信地拍着胸脯说:“流式输出,前端后端我全栈!” 如果还遇到怪问题,八成是换行符或者CORS,再不行就在评论区贴代码,咱们一起捉虫。觉得有用的话,点个⭐收藏,下次再遇到SSE,翻出这篇文章复习下再动手~ —— 依然是你那个爱踩坑的一名程序媛 👩‍💻