執行環境套件管理器打包器測試執行器程式碼檢查格式化指南參考部落格意見回饋

使用 Bun 的伺服器傳送事件(SSE)

伺服器傳送事件允許你通過單個 HTTP 響應向瀏覽器推送文本事件流。客戶端使用 EventSource 消費這些事件。

要在 Bun 中實現 SSE 端點,請傳回一個正文為流式源的 Response,並將 Content-Type 標頭設定為 text/event-stream。

使用非同步生成器

在 Bun 中,new Response 可直接接受非同步生成器函式。非同步生成器通常是編寫 SSE 端點最簡單的方式。每次 yield 都會向客戶端重新整理一個數據塊。如果客戶端斷開連線,生成器的 finally 塊會執行,以便你進行清理。

server.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() 中設定的所有資源。

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