还在纠结大模型聊天机器人为什么能 “边说边出字”?想知道为什么这道题成了 25 年大厂面试的香饽饽?吃透 “流式输出”—— 从原理到全栈实战,让你的前端项目体验直逼 ChatGPT!
一、为什么流式输出成了大厂必考题?
先看一组趋势:23 年 AI 大模型爆火,24 年聚焦推理优化,25 年AI Agent(智能代理)成为新风口。而流式输出,正是 AI 产品用户体验的 “灵魂技能”。
核心原因:
- 大模型的特性决定的:LLM(大语言模型)生成内容时,是像人说话一样 “一个词一个词蹦”(专业叫 “token-by-token” 生成),不会一次性吐出完整内容。
- 前端的核心职责:用户体验。流式输出能让用户 “边等边看”,而不是盯着空白屏幕发呆 —— 这正是大厂考核 “用户体验设计” 的关键考点。
二、为什么需要流式输出?举个栗子就懂
想象两个聊天机器人:
- 传统方式:你发消息后,屏幕空白 3 秒,突然一次性弹出 100 字回复。
- 流式输出:你发消息后,屏幕立刻开始 “打字”,1 秒出几个字,3 秒内慢慢显示完 100 字。
你选哪个?显然是后者!
这就是流式输出的核心价值:把 “等待” 变成 “渐进式体验” ,用心理学降低用户的焦虑感 —— 哪怕总耗时一样,体验也天差地别。
流式输出的本质:从 “一次性” 到 “分批次”
传统的 HTTP 请求是 “一问一答”:
- 前端发请求 → 后端处理 → 后端一次性返回所有数据 → 前端显示。
流式输出则是 “边处理边返回”:
- 前端发请求 → 后端启动处理 → 后端生成一点数据就返回一点 → 前端收到一点就显示一点 → 直到全部返回。
就像喝水:传统方式是 “等水壶装满再一口气喝完”,流式输出是 “打开水龙头,边接边喝”。
三、前端如何实现流式输出?核心技术揭秘
前端要做的是:持续接收后端分批发来的数据,并实时更新到页面。现代浏览器提供了一个专门的 API——SSE(Server-Sent Events,服务器发送事件) 。
简单说:SSE 就是 “后端给前端发消息的通道”
- 前端创建一个 “事件源”,连接后端的 SSE 接口;
- 后端通过这个接口,随时给前端 “推消息”;
- 前端收到消息后,立刻更新页面。
四、全栈实战:从 0 实现流式输出
我们用前端 HTML + 后端 Node.js(Express) 实现一个 “实时报时” 的流式输出案例:后端每秒推一次当前时间,前端实时显示。
步骤 1:搭建后端(Node.js+Express)
1. 初始化项目
# 初始化npm
npm init -y
# 安装express框架
npm i express
2. 后端代码(index.js)
// 引入express框架
const express = require('express');
const app = express(); // 创建后端应用
// 1. 前端页面路由:访问localhost:3000时返回HTML页面
app.get('/', (req, res) => {
// 发送前端页面(后面会创建index.html)
res.sendFile(__dirname + '/index.html');
});
// 2. SSE接口:专门用于后端向前端推数据
app.get('/sse', (req, res) => {
// 设置响应头:告诉前端这是SSE流
res.set({
'Content-Type': 'text/event-stream', // 固定格式:声明是事件流
'Cache-Control': 'no-cache', // 禁止缓存,确保数据实时
'Connection': 'keep-alive' // 保持连接,不中断
});
res.flushHeaders(); // 立即发送这些响应头
// 每1秒向前端推一次当前时间(模拟流式输出)
setInterval(() => {
// SSE数据格式:必须以`data: 内容\n\n`结尾
const time = new Date().toLocaleTimeString(); // 当前时间
res.write(`data: <p>${time}</p>\n\n`); // 推送给前端
}, 1000);
});
// 启动服务器,监听3000端口
app.listen(3000, () => {
console.log('后端启动成功:http://localhost:3000');
});
步骤 2:编写前端页面(index.html)
<!DOCTYPE html>
<html>
<head>
<title>流式输出演示</title>
</head>
<body>
<h1>实时时间(流式输出)</h1>
<div id="messages"></div> <!-- 用于显示流式数据 -->
<script>
// 1. 创建SSE连接:连接后端的/sse接口
const source = new EventSource('/sse');
// 2. 监听后端推送的消息
source.onmessage = function(event) {
// event.data就是后端推来的内容(这里是带<p>标签的时间)
const messagesDiv = document.getElementById('messages');
messagesDiv.innerHTML += event.data; // 实时添加到页面
};
</script>
</body>
</html>
运行效果
- 终端执行
node index.js
启动后端; - 浏览器打开
http://localhost:3000
,会看到页面每秒新增一行当前时间 —— 这就是最基础的流式输出!
五、关键技术点解析
1. 后端 SSE 接口的 3 个核心响应头
res.set({
'Content-Type': 'text/event-stream', // 告诉前端:这是事件流,要持续接收
'Cache-Control': 'no-cache', // 不缓存,每次都要新数据
'Connection': 'keep-alive' // 保持TCP连接不关闭,才能持续推数据
});
这三个头是 “保持连接” 的关键,缺一不可。
2. 后端推数据的格式
必须用 data: 内容\n\n
格式,比如:
res.write(`data: 这是一条消息\n\n`); // 正确格式
\n\n
是结束符,告诉前端 “这一段数据发完了”。
3. 前端的 EventSource
const source = new EventSource('/sse'); // 连接后端SSE接口
source.onmessage = (event) => { ... } // 收到数据时触发
- 自动保持连接,断了会自动重连;
- 只能接收后端推送,不能主动发消息(适合单向流场景,如大模型回复)。
六、总结
对前端来说,流式输出的本质是:用 SSE(或 WebSocket)建立 “长连接”,实时接收后端分批数据,并即时更新 UI。
它不只是一种技术,更是一种 “用户体验设计思维”—— 在等待不可避免时,想办法让用户 “不觉得在等”。
不止 AI 聊天:
- 长文本加载:小说网站 “边翻页边加载” 下一章内容;
- 实时日志:后台系统实时显示服务器日志,不用刷新页面;
- 进度提示:文件上传时,实时显示 “已上传 30%”“50%”。
25 年大厂考这个,正是因为 AI 时代,这种 “渐进式体验” 会成为前端的基础能力。现在就动手试试上面的代码,感受一下流式输出的魅力吧!