W3docs

Eventos enviados por el servidor en JavaScript (EventSource)

Aprende los Server-Sent Events de JavaScript con la API EventSource — recibe un flujo unidireccional de actualizaciones del servidor sobre HTTP, con reconexión automática y eventos con nombre.

Los Server-Sent Events (SSE) son una forma estándar de que un servidor envíe un flujo continuo y unidireccional de actualizaciones de texto al navegador a través de una única conexión HTTP persistente. En lugar de que el cliente sondee repetidamente un endpoint con fetch preguntando "¿hay algo nuevo?", la conexión permanece abierta y el servidor escribe mensajes cada vez que tiene algo que enviar. El navegador expone esto mediante la interfaz integrada EventSource, por lo que no se necesita ninguna librería externa para consumir un flujo.

SSE destaca cuando las actualizaciones fluyen en una sola dirección — del servidor al cliente. Los feeds de noticias en vivo, las notificaciones, las barras de progreso de compilación o subida, los paneles de monitoreo y los tickers de bolsa son todos casos de uso ideales.

Recibir un flujo con EventSource

Abrir un flujo requiere una sola línea. Se crea un EventSource apuntando a un endpoint del servidor y luego se adjuntan manejadores para los eventos que dispara.

const es = new EventSource('/events');

es.onmessage = (event) => {
  console.log('New message:', event.data);
};

es.onopen = () => {
  console.log('Connection opened');
};

es.onerror = (error) => {
  console.log('Connection error:', error);
};

Vale la pena destacar algunas cosas:

  • La conexión se abre en cuanto se construye el EventSource.
  • onmessage se dispara para cada mensaje predeterminado (sin nombre) que envía el servidor. El contenido es siempre un string, disponible en event.data.
  • onopen se dispara una vez que la conexión está establecida (y de nuevo tras una reconexión).
  • onerror se dispara cuando la conexión se cae o falla. A diferencia de un fetch fallido, esto no significa necesariamente que se deba abandonar — el navegador generalmente intentará reconectarse por sí solo (más sobre esto a continuación).

Aquí hay un ejemplo un poco más completo que analiza cargas JSON y actualiza la página:

const es = new EventSource('/notifications');

es.onmessage = (event) => {
  const data = JSON.parse(event.data);
  const li = document.createElement('li');
  li.textContent = data.message;
  document.querySelector('#feed').append(li);
};

El formato de transmisión

El servidor responde con el tipo de contenido text/event-stream y escribe texto UTF-8 plano en un formato simple basado en líneas. Cada mensaje es un bloque de líneas campo: valor, y una línea en blanco marca el fin de un mensaje.

data: Hello there

data: First line
data: Second line

event: priceUpdate
data: {"symbol":"ACME","price":42.10}

id: 42
data: This message has an id

retry: 10000
data: Reconnect after 10 seconds next time

Los campos reconocidos son:

  • data: — la carga del mensaje. Si un mensaje tiene varias líneas data:, se unen con un salto de línea (\n) antes de entregarse como event.data.
  • event: — un nombre de evento opcional. Sin él, el mensaje se entrega a onmessage.
  • id: — un identificador opcional que el navegador recuerda para la reconexión.
  • retry: — el retardo de reconexión en milisegundos.

Las líneas que comienzan con dos puntos (:) son comentarios y se ignoran — los servidores suelen enviarlos como pings de mantenimiento de conexión.

Eventos con nombre

Por defecto, cada mensaje va a onmessage. Un servidor puede en cambio etiquetar un mensaje con un campo event:, y el cliente escucha ese nombre específico con addEventListener. Esto permite enrutar diferentes tipos de actualizaciones a distintos manejadores sobre la misma conexión.

Dado un flujo como este:

event: priceUpdate
data: {"symbol":"ACME","price":42.10}

event: newsAlert
data: Markets closed early today

data: a plain default message

Se configuraría el cliente de la siguiente manera:

const es = new EventSource('/market');

es.addEventListener('priceUpdate', (event) => {
  const { symbol, price } = JSON.parse(event.data);
  console.log(`${symbol} is now ${price}`);
});

es.addEventListener('newsAlert', (event) => {
  console.log('News:', event.data);
});

// Messages with no `event:` field still land here.
es.onmessage = (event) => {
  console.log('Default message:', event.data);
};

Reconexión automática

Esta es la característica que distingue a SSE de implementar manualmente la transmisión con fetch. Si la conexión se cae — una red inestable, un reinicio del servidor, un timeout del proxy — el navegador se reconecta automáticamente después de una breve pausa. No es necesario escribir ningún bucle de reintento.

Para hacer que la reanudación sea confiable, el navegador lleva un seguimiento del último id: recibido. Al reconectarse, envía ese valor de vuelta en una cabecera de solicitud Last-Event-ID, para que el servidor pueda retomar exactamente donde se dejó el flujo en lugar de repetirlo todo.

GET /events HTTP/1.1
Last-Event-ID: 42
Accept: text/event-stream

El servidor también puede ajustar el retardo antes de la próxima reconexión enviando un campo retry: (en milisegundos):

retry: 5000
data: The browser will wait 5 seconds before reconnecting if dropped
Nota

La reconexión automática solo ocurre mientras la conexión se considera recuperable. Si el servidor responde a una reconexión con un error HTTP como 204 No Content o un estado 4xx, el navegador trata el flujo como finalizado y deja de intentarlo.

Estado de la conexión y cierre

Un EventSource expone su estado actual mediante la propiedad readyState, que coincide con tres constantes:

  • EventSource.CONNECTING (0) — conectando o esperando para reconectarse.
  • EventSource.OPEN (1) — conectado y recibiendo.
  • EventSource.CLOSED (2) — cerrado y no se reconectará.

Cuando ya no se necesita el flujo, se llama a es.close(). Esto cierra la conexión inmediatamente y — de forma importante — el navegador no se reconectará después.

const es = new EventSource('/events');

// Stop listening after 30 seconds.
setTimeout(() => {
  es.close();
  console.log('readyState is now', es.readyState); // 2 (CLOSED)
}, 30000);

Solicitudes de origen cruzado y credenciales

Por defecto, un EventSource sigue la política del mismo origen. Para transmitir desde un origen diferente, el servidor debe enviar las cabeceras CORS apropiadas (como Access-Control-Allow-Origin).

Las cookies no se envían en solicitudes de origen cruzado a menos que se opte por ello. Se pasa la opción withCredentials y hay que asegurarse de que el servidor permita credenciales en su configuración CORS:

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

Una nota sobre el lado del servidor

SSE es principalmente una característica del navegador, pero el servidor debe cooperar. Sea cual sea el lenguaje que se use, la receta es la misma: responder con Content-Type: text/event-stream, mantener la conexión abierta en lugar de finalizar la respuesta, y escribir fragmentos con formato de evento a medida que los datos estén disponibles.

Aquí hay un ejemplo mínimo de Node.js para mostrar su estructura:

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 += 1;
    res.write(`id: ${id}\n`);
    res.write(`data: The time is ${new Date().toISOString()}\n\n`);
  }, 1000);

  // Stop the timer when the client disconnects.
  req.on('close', () => clearInterval(timer));
}).listen(3000);

Cada fragmento termina con una línea en blanco (\n\n) para completar el mensaje. El cliente conectado con new EventSource('http://localhost:3000') recibiría una actualización por segundo.

Limitaciones

Advertencia

SSE solo transporta texto (UTF-8) — no existe un tipo de trama binaria, por lo que no se puede transmitir bytes sin procesar como imágenes o audio sin codificarlos primero. También es estrictamente unidireccional: para enviar datos al servidor aún se necesita una solicitud normal mediante fetch o XMLHttpRequest, o una conexión WebSocket de dúplex completo.

Otro límite práctico: sobre HTTP/1.1, los navegadores limitan el número de conexiones simultáneas a un mismo origen a aproximadamente seis. Como cada EventSource mantiene una conexión abierta, abrir muchas pestañas al mismo sitio puede agotar ese presupuesto. Esto es mucho menos preocupante sobre HTTP/2, donde muchos flujos se multiplexan en una sola conexión.

SSE vs. WebSockets

SSE y WebSockets ambos entregan datos en tiempo real, pero resuelven problemas diferentes.

Server-Sent EventsWebSockets
DirecciónUnidireccional (servidor → cliente)Bidireccional (dúplex completo)
ProtocoloHTTP planoActualización a ws:// / wss://
DatosSolo texto (UTF-8)Texto y binario
ReconexiónAutomática, integradaImplementar manualmente
Ideal paraFeeds, notificaciones, progresoChat, juegos, edición colaborativa

Usa SSE cuando el navegador solo necesita escuchar — notificaciones, feeds en vivo, paneles de control, actualizaciones de progreso. Su reconexión automática, transporte HTTP plano y pequeña API de cliente lo convierten en la herramienta más sencilla para el trabajo. Usa WebSockets cuando el cliente también necesite enviar con frecuencia, cuando se necesiten datos binarios, o cuando la latencia por mensaje importe, como en apps de chat y juegos multijugador.

Información

Si los datos ya residen detrás de un flujo asíncrono basado en promesas, combinar los manejadores de SSE con async/await para cualquier solicitud de seguimiento (por ejemplo, obtener detalles completos cuando llega una notificación ligera) mantiene el código limpio y legible.

Pon a prueba tus conocimientos

Práctica
¿Los Server-Sent Events proporcionan comunicación en qué dirección?
¿Los Server-Sent Events proporcionan comunicación en qué dirección?
Práctica
¿Cuál es una ventaja clave de EventSource sobre la transmisión manual con fetch?
¿Cuál es una ventaja clave de EventSource sobre la transmisión manual con fetch?
Práctica
¿Qué afirmaciones sobre el formato de transmisión SSE son verdaderas?
¿Qué afirmaciones sobre el formato de transmisión SSE son verdaderas?
Was this page helpful?