Start · Sprachen · JavaScript · Referenz · EventSource

EventSource

Schnittstelle

Öffnet eine dauerhafte HTTP-Verbindung zu einem Server und empfängt über Server-sent Events (<code>text/event-stream</code>) fortlaufend Nachrichten.

seit JavaScript Baseline: alle modernen Browser (seit 2020 inkl. E Kategorie: webapi

Signatur

interface EventSource extends EventTarget

Beschreibung

Die Schnittstelle EventSource ist die clientseitige Schnittstelle für Server-sent Events (SSE). Mit new EventSource(url) wird eine dauerhafte HTTP-Verbindung aufgebaut. Der Server sendet darüber Ereignisse im Format text/event-stream. Jede empfangene Nachricht löst im Browser ein Ereignis aus: standardmäßig message, bei benannten Ereignissen (event: name) ein Ereignis mit genau diesem Namen.

SSE ist unidirektional, Daten fließen also nur vom Server zum Client. Damit eignet es sich für Live-Ticker, Benachrichtigungen, Fortschrittsanzeigen, Log-Streams oder das Streamen von Antworten großer Sprachmodelle (LLMs). Anders als bei WebSockets läuft alles über gewöhnliches HTTP. Der Browser baut abgebrochene Verbindungen automatisch neu auf und schickt dabei die zuletzt empfangene Ereignis-ID im Header Last-Event-ID mit.

Wichtige Bestandteile:

  • Eigenschaften: readyState (0 = CONNECTING, 1 = OPEN, 2 = CLOSED), url und withCredentials.
  • Methode: close() beendet die Verbindung endgültig. Danach findet kein automatischer Neuaufbau mehr statt.
  • Ereignisse: open, message, error sowie beliebige benannte Ereignisse. Registrieren lassen sie sich über addEventListener() oder die Eigenschaften onopen, onmessage und onerror.

Benannte Ereignisse lassen sich nur mit addEventListener() abonnieren. Der Handler onmessage erhält ausschließlich Nachrichten ohne event:-Feld.

Parameter

Name Typ Default Beschreibung
$url Pflicht string | URL Adresse des Endpunkts, der text/event-stream liefert. Relative Angaben werden relativ zur Adresse des Dokuments aufgelöst.
$options { withCredentials?: boolean } { withCredentials: false } Konfiguration. Mit withCredentials: true werden bei Anfragen an andere Ursprünge (Cross-Origin) Cookies und Anmeldedaten mitgesendet. Der Server muss das per CORS erlauben.

Rückgabewert

Typ
EventSource
Beschreibung
Eine neue EventSource-Instanz. Sie beginnt sofort mit dem Verbindungsaufbau (readyState === 0).

Beispiele

Verbindung öffnen und Nachrichten empfangen

const source = new EventSource('/api/stream');

source.onopen = () => {
  console.log('Verbunden, readyState:', source.readyState);
};

source.onmessage = (event) => {
  // event.data ist immer ein String
  const payload = JSON.parse(event.data);
  console.log('Neue Nachricht:', payload, 'ID:', event.lastEventId);
};

source.onerror = () => {
  if (source.readyState === EventSource.CONNECTING) {
    console.warn('Verbindung unterbrochen – Browser verbindet neu …');
  } else if (source.readyState === EventSource.CLOSED) {
    console.error('Verbindung endgültig geschlossen.');
  }
};
Verbunden, readyState: 1 Neue Nachricht: { text: 'Hallo' } ID: 42

Benannte Ereignisse mit addEventListener abonnieren und wieder entfernen

const source = new EventSource('https://api.example.com/notifications', {
  withCredentials: true
});

// Server sendet z. B.:
// event: order-shipped
// data: {"orderId": 1234}

const onShipped = (event) => {
  const { orderId } = JSON.parse(event.data);
  console.log(`Bestellung ${orderId} wurde versandt`);
};

source.addEventListener('order-shipped', onShipped);

// Später: nur diesen Listener entfernen
setTimeout(() => {
  source.removeEventListener('order-shipped', onShipped);
}, 60_000);
Bestellung 1234 wurde versandt

Fortschrittsanzeige mit sauberem Schließen

const trackJob = (jobId) => new Promise((resolve, reject) => {
  const source = new EventSource(`/jobs/${jobId}/progress`);

  source.addEventListener('progress', (e) => {
    console.log(`Fortschritt: ${e.data} %`);
  });

  source.addEventListener('done', (e) => {
    source.close(); // verhindert automatischen Neuaufbau
    resolve(JSON.parse(e.data));
  });

  source.addEventListener('error', () => {
    if (source.readyState === EventSource.CLOSED) {
      reject(new Error('Stream abgebrochen'));
    }
  });
});

const result = await trackJob('abc123');
console.log('Ergebnis:', result);
Fortschritt: 25 % Fortschritt: 100 % Ergebnis: { file: 'report.pdf' }

Minimaler Node.js-Server (Gegenstück)

import http from 'node:http';

http.createServer((req, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    Connection: 'keep-alive'
  });
  let id = 0;
  const timer = setInterval(() => {
    id++;
    res.write(`id: ${id}\nretry: 5000\ndata: ${JSON.stringify({ time: Date.now() })}\n\n`);
  }, 1000);
  req.on('close', () => clearInterval(timer));
}).listen(3000);

// Wichtig · Fallstricke

  • Nur GET und keine eigenen Header: EventSource kann weder Authorization-Header noch einen Anfragetext (Body) senden. Zur Authentifizierung dienen Cookies (withCredentials) oder ein Token in der URL. Ein Token in der URL landet allerdings in Server-Logs. Wer POST oder eigene Header braucht, nutzt stattdessen fetch() mit ReadableStream.
  • Verbindungslimit bei HTTP/1.1: Browser erlauben pro Domain nur etwa 6 gleichzeitige Verbindungen, und das gilt über alle Tabs hinweg. Mehrere offene Tabs mit SSE können so die Seite blockieren. HTTP/2 hebt diese Grenze praktisch auf.
  • Automatischer Neuaufbau: Nach einem Netzwerkfehler verbindet der Browser selbstständig neu. Den Abstand legt der Server über das Feld retry: fest (in Millisekunden). Antwortet der Server mit einem HTTP-Status außer 200 oder mit einem falschen Content-Type, wird die Verbindung endgültig geschlossen (readyState === 2). Der Server kann den Neuaufbau auch gezielt mit Status 204 unterbinden.
  • Immer close() aufrufen, wenn der Stream nicht mehr gebraucht wird, etwa beim Unmount einer Komponente. Sonst bleiben Verbindungen offen, und durch die ständigen Neuaufbau-Versuche entstehen Speicherlecks.
  • Proxys und Puffer: Reverse-Proxys wie nginx puffern Antworten mitunter, sodass Nachrichten verzögert ankommen. Abhilfe schafft der Header X-Accel-Buffering: no. Kompression (gzip) kann ebenfalls puffern.
  • Sicherheit: event.data ist ein unbehandelter String. Niemals ungeprüft per innerHTML einfügen, sonst droht Cross-Site-Scripting (XSS). Bei Cross-Origin-Anfragen gilt CORS. Außerdem muss eine gesetzte Content Security Policy den Endpunkt in connect-src erlauben.
  • Kompatibilität: Wird von allen modernen Browsern unterstützt, auch in Web Workern. Der Internet Explorer kennt kein EventSource. Node.js bringt keine globale Implementierung mit (nur experimentell); dort helfen Pakete wie eventsource.