0%

EventSource的局限

JavaScript 标准提供了 EventSource 接口,用于与服务器发送事件(Server-Sent Events,SSE)接口进行通信。

1
2
3
4
5
6
7
8
9
var evtSource = new EventSource("sse.php");
var eventList = document.querySelector("ul");

evtSource.onmessage = function (e) {
var newElement = document.createElement("li");

newElement.textContent = "message: " + e.data;
eventList.appendChild(newElement);
};

上面的代码片段来源于 MDN,展示了 EventSource 的基本用法:通过后端接口 "sse.php" 接收事件,并在 onmessage 回调中处理事件(本例中是将事件数据添加到页面的列表中)。

然而,EventSource 存在一些局限性,最明显的是它仅支持连接到 GET 接口,无法使用 POST 等其他 HTTP 方法,也无法通过 HTTP Body 传输数据。这对于一些需要通过 POST 请求发送参数的应用场景会受到限制。

在生成式 AI 应用中,OpenAI 的对话接口支持流式传输数据,这种传输方式通常实现为 SSE。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import OpenAI from "openai";

const openai = new OpenAI();

async function main() {
const stream = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [{ role: "user", content: "Say this is a test" }],
stream: true,
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content || "");
}
}

main();

上述代码是 OpenAI 提供的 JavaScript SDK 中流式接口的示例,返回一个异步生成器,通过 for await…of 语法逐步处理返回的消息流。

在某些场景下,前端可能不会直接使用 OpenAI 的 SDK(例如出于安全原因不希望在浏览器中暴露 API key),而 OpenAI 的接口是通过 POST 方法发送 JSON 数据,因此不能直接使用 EventSource 来接收流式数据。这时的解决方案是通过 fetch 获取接口的响应 ReadableStream,手动解析流并封装为一个异步生成器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
async function* chatStream(url, body) {
const resp = await fetch(url, {
method: "POST",
body: JSON.stringify({
body,
}),
});
const reader = resp.body.getReader();
let line = "";
while (true) {
const { value, done } = await reader.read();
if (done) break;
const decoder = new TextDecoder();
const text = decoder.decode(value, { stream: true });
line += text;
if (line.endsWith("\n\n")) {
yield line;
line = "";
}
}
}

该代码展示了如何通过 fetch 发起 POST 请求,并通过 ReadableStream 解析服务器的流式响应。每当接收到完整的一段消息(以 \n\n 结尾),将其作为一条独立的消息返回给调用者。这样可以有效地模拟 EventSource 的行为,支持更多 HTTP 方法和自定义请求。

扫码加入技术交流群🖱️
QR code