SSE 前后端示例
前端使用一个轻量化的 fetch 封装库 @microsoft/fetch-event-source,替代有诸多限制的原生 EventSource API
useEffect(() => {
const ctrl = new AbortController();
fetchEventSource(`/api/stream?t=${Date.now()}`, {
signal: ctrl.signal,
onmessage(ev) {
setData((prev) => prev + ev.data);
},
onclose() {},
}).then(() => {});
return () => {
ctrl.abort();
};
}, [retryCount]);
后端使用 ReadableStream 推送流数据
import { NextRequest } from "next/server";
export const runtime = "nodejs"; // 确保使用 Node runtime
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const t = searchParams.get("t");
const stream = new ReadableStream({
async start(controller) {
const encoder = new TextEncoder();
const send = (data: string) => {
controller.enqueue(encoder.encode(`data: ${data}`));
// controller.enqueue(encoder.encode(`${data}`)); // 在 data 字段后面追加内容
controller.enqueue(encoder.encode(`\n\n`)); // 以 两个换行符 结束
};
try {
const tokens = `${t ? `${t} ` : ""}SSE Stream Demo`.split("");
for (const t of tokens) {
send(t);
await new Promise((r) => setTimeout(r, 100));
}
} catch (err) {
send(`error: ${(err as Error).message}`);
} finally {
controller.close();
}
},
});
return new Response(stream, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache, no-transform",
Connection: "keep-alive",
},
});
}