Skip to main content
服务器发送事件 允许您通过单个 HTTP 响应向浏览器推送一系列文本事件。客户端通过 EventSource 来接收这些事件。 在 Bun 中,您可以通过返回一个主体为流式来源的 Response,并将 Content-Type 头设置为 text/event-stream 来实现 SSE 端点。
Bun.serve 默认会在 10 秒 后关闭空闲连接。一个安静的 SSE 流会被视为空闲,因此下面的示例调用了 server.timeout(req, 0) 来禁用此流的超时。详情请参见 idleTimeout

使用异步生成器

在 Bun 中,new Response 直接接受一个异步生成器函数。这通常是编写 SSE 端点的最简单方法——每次 yield 会向客户端刷新一块数据,如果客户端断开连接,生成器的 finally 块会执行,从而可以清理资源。
https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217server.ts
Bun.serve({
  port: 3000,
  routes: {
    "/events": (req, server) => {
      // SSE 流事件间通常比较安静。
      // 默认情况下,Bun.serve 会在 10 秒无活动后关闭连接。
      // 禁用该请求的空闲超时,以保持流无限期打开。
      server.timeout(req, 0);

      return new Response(
        async function* () {
          yield `data: connected at ${Date.now()}\n\n`;

          // 每 5 秒发送一个 tick,直到客户端断开连接。
          // 客户端断开时,生成器被返回(取消),此循环自动停止。
          while (true) {
            await Bun.sleep(5000);
            yield `data: tick ${Date.now()}\n\n`;
          }
        },
        {
          headers: {
            "Content-Type": "text/event-stream",
            "Cache-Control": "no-cache",
          },
        },
      );
    },
  },
});

使用 ReadableStream

如果您的事件来自回调——消息代理、定时器、外部推送——而不是线性的 await 流,ReadableStream 通常更合适。当客户端断开连接时,Bun 会自动调用流的 cancel() 方法,因此您可以在 start() 中设置的资源进行释放。
https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217server.ts
Bun.serve({
  port: 3000,
  routes: {
    "/events": (req, server) => {
      server.timeout(req, 0);

      let timer: Timer;
      const stream = new ReadableStream({
        start(controller) {
          controller.enqueue(`data: connected at ${Date.now()}\n\n`);

          timer = setInterval(() => {
            controller.enqueue(`data: tick ${Date.now()}\n\n`);
          }, 5000);
        },
        cancel() {
          // 当客户端断开连接时自动调用。
          clearInterval(timer);
        },
      });

      return new Response(stream, {
        headers: {
          "Content-Type": "text/event-stream",
          "Cache-Control": "no-cache",
        },
      });
    },
  },
});