fetch-event-source+生成器:极简封装大模型SSE流式请求
上一篇文章和大家聊了JS生成器的核心用法,不少同学留言想看看实际项目落地场景——尤其是大模型流式输出、SSE请求这类高频需求。
本文带来极简版封装方案:基于微软@microsoft/fetch-event-source解决原生SSE不支持POST的痛点,结合异步生成器实现“边接收边处理”的流式逻辑,去掉冗余代码、保留核心功能,新手也能快速上手落地。
一、核心工具选型原因(极简版)
1. 原生fetch处理SSE的短板
原生fetch仅支持GET请求处理SSE,而大模型对话需传递长文本、文件ID等复杂参数,GET请求的URL长度限制完全无法满足需求,这是核心痛点。
2. @microsoft/fetch-event-source的核心价值
微软官方维护的该库,最关键的作用是支持POST方法处理SSE,同时兼容原生fetch用法,保留SSE自动重连、事件监听特性,学习成本极低。
3. 异步生成器的适配性
异步生成器(async function*)的yield关键字,天然适配流式数据“分段返回”的需求,无需等待全量数据加载,还能通过for...await...of优雅遍历,完美契合“边接收边渲染”场景。
二、环境准备:安装依赖
仅需安装核心依赖,无额外冗余依赖,执行以下命令即可:
# npm
npm install @microsoft/fetch-event-source --save
# yarn
yarn add @microsoft/fetch-event-source三、核心封装:异步生成器封装SSE请求
聚焦核心逻辑,去掉复杂容错与扩展,保留“POST请求+流式迭代”核心能力,代码极简且可直接复用:
import { fetchEventSource, EventSourceMessage } from '@microsoft/fetch-event-source';
// 定义流式响应类型(按需简化,贴合实际接口字段)
export type ResponseStreamResult = {
data: string;
};
/**
* 极简版:异步生成器封装SSE POST流式请求
* @param message 对话内容
* @param fileIds 可选文件ID列表(适配多文件上传场景)
*/
export async function* chatStream(
message: string,
fileIds?: string[]
): AsyncGenerator<ResponseStreamResult> {
// 创建可读流,连接SSE与生成器
const stream = new ReadableStream<ResponseStreamResult>({
start(controller) {
fetchEventSource('/api/llm', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message, fileIds: fileIds || [] }),
// 接收SSE消息并写入流
onmessage: (event: EventSourceMessage) => {
try {
const result = JSON.parse(event.data) as ResponseStreamResult;
if (result.data) controller.enqueue(result); // 非空数据入流
} catch (e) {
console.warn('数据解析失败:', e);
}
},
onclose: () => controller.close(), // 流结束关闭控制器
onerror: (err) => controller.error(err) // 错误传递
});
}
});
// 读取流数据并通过yield返回
const reader = stream.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
yield value!;
}
reader.releaseLock(); // 释放资源
}四、使用示例:两种核心场景
场景1:自动迭代(边接收边渲染)
最常用场景,通过for...await...of遍历生成器,实时更新UI,适配大模型对话、实时日志等需求:
/**
* 处理大模型对话,实时渲染流式结果
*/
async function handleChat() {
const message = '请解释什么是JS异步生成器';
try {
// 遍历生成器,实时获取并渲染数据
for await (const chunk of chatStream(message)) {
console.log('收到流式数据:', chunk.data);
// 简化版UI渲染:追加到对话容器(实际可封装为组件)
document.getElementById('chat-box')!.innerHTML += chunk.data;
}
console.log('流式请求完成');
} catch (err) {
console.error('请求失败:', err);
}
}场景2:手动控制(逐段获取)
如需手动控制输出节奏(如点击按钮加载下一段),通过next()方法手动触发:
async function handleControlledStream() {
const generator = chatStream('请分步讲解SSE流式请求');
let nextResult: IteratorResult<ResponseStreamResult>;
// 手动获取下一段数据(绑定到按钮点击事件)
const getNextChunk = async () => {
nextResult = await generator.next();
if (nextResult.done) {
console.log('输出完成');
return;
}
// 渲染单段数据
document.getElementById('chat-box')!.innerHTML += nextResult.value.data;
};
// 绑定按钮事件(简化DOM操作,实际需做空值判断)
document.getElementById('next-btn')!.addEventListener('click', getNextChunk);
}五、核心亮点与扩展建议
1. 简化版核心亮点
代码极简:剔除冗余逻辑,核心代码仅30余行,易理解、易修改、易复用;
用法优雅:封装后隐藏底层SSE细节,上层仅需通过生成器API操作,降低使用成本;
落地性强:直接复制可用于生产环境基础场景,按需扩展即可上线。
2. 生产环境扩展建议
若需用于生产环境,可在简化版基础上补充两点核心能力,不影响原有逻辑:
取消请求:通过
AbortController绑定信号,支持用户主动取消流式请求;详细容错:补充接口异常提示、数据格式校验、网络中断处理,提升稳定性。
总结
本文通过极简封装,结合@microsoft/fetch-event-source与异步生成器,高效解决了大模型SSE流式请求的核心痛点。既突破了原生SSE不支持POST的限制,又借助生成器实现了流式数据的优雅处理,兼顾简洁性与实用性。
