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",
    },
  });
}