LEARNING OBJECTIVES โต
- Master the syntax and configuration options of the
new EventSource(url, options)constructor. - Understand the complete lifecycle state transitions (
readyState:0=CONNECTING,1=OPEN,2=CLOSED). - Handle standard stream events using
onopen,onmessage, andonerror. - Extract message payloads, origins, and metadata from the incoming
MessageEventobject. - Gracefully terminate persistent streams using
.close()to prevent background socket exhaustion and memory leaks.
๐ The Mental Model & Story (Intuitive Foundation)
Think of the EventSource object as an Automated Water Pipeline Contractor.
When you write:
const pipeline = new EventSource('/water-supply');
You are instructing the contractor to lay a dedicated water pipe from the municipal reservoir (/water-supply) directly into your kitchen sink.
+------------------+ +------------------------------------+
| Browser Client | | Municipal Server |
| (EventSource) | | (/water-supply) |
+------------------+ +------------------------------------+
| |
| 1. Opens Valve (readyState: 0 - CONNECTING) |
|================================================>|
| |
| 2. Water Begins Flowing (readyState: 1 - OPEN) |
|<------------------------------------------------| [fires onopen]
| |
| 3. Water Drops Deliver (data: payload) |
|<------------------------------------------------| [fires onmessage]
| |
| 4. Earthquake / Pressure Drop (Network Loss) |
| [fires onerror] |
| Contractor automatically attempts repair... |
| (readyState: 0 - CONNECTING) |
|================================================>|
| |
| 5. You call pipeline.close() |
| Contractor shuts main shutoff valve forever. |
| (readyState: 2 - CLOSED, Zero Reconnects) |
onopen(Water starts flowing): The contractor confirms the pipe is open and pressurized.onmessage(Water arrives): Every time water drops through the nozzle, your faucet sensor captures it.onerror(Pressure drop or pipe break): If the pipe ruptures (server restarts or Wi-Fi drops), the contractor automatically attempts to repair and reconnect the pipe in the background without you having to write any retry code!pipeline.close()(Shut off the main valve): When you leave the house or navigate away, you explicitly close the valve. This tells the contractor to permanently stop repairing and free up municipal water pressure.
Technical Deep Dive & Specifications
The EventSource Interface & IDL Specification
According to the WHATWG HTML Living Standard, the EventSource interface inherits from EventTarget:
[Exposed=(Window,Worker)]
interface EventSource : EventTarget {
constructor(USVString url, optional EventSourceInit eventSourceInitDict = {});
readonly attribute USVString url;
readonly attribute boolean withCredentials;
// Ready-state codes
const unsigned short CONNECTING = 0;
const unsigned short OPEN = 1;
const unsigned short CLOSED = 2;
readonly attribute unsigned short readyState;
// Lifecycle Event Handlers
attribute EventHandler onopen;
attribute EventHandler onmessage;
attribute EventHandler onerror;
undefined close();
};
dictionary EventSourceInit {
boolean withCredentials = false;
};
Connection State Machine (readyState)
The readyState attribute indicates the exact state of the HTTP streaming connection:
+--------------------------+
| new EventSource(url) |
+--------------------------+
|
v
+-------------------------------+
| 0: EventSource.CONNECTING |<---------------+
+-------------------------------+ |
| |
HTTP 200 OK & | Network Error / |
Content-Type: text/... | Transient Drop |
v |
+-------------------------------+ |
| 1: EventSource.OPEN |-----------------+
+-------------------------------+
|
Call .close() or
HTTP 204 No Content
|
v
+-------------------------------+
| 2: EventSource.CLOSED |
+-------------------------------+
| Constant | Value | Description |
|---|---|---|
EventSource.CONNECTING |
0 |
The connection is currently being established or is actively reconnecting after a dropped connection. |
EventSource.OPEN |
1 |
The connection is open, healthy, and ready to dispatch incoming stream events. |
EventSource.CLOSED |
2 |
The stream was permanently terminated via eventSource.close(), or a fatal server error (e.g. HTTP 404 or HTTP 401) occurred. No further reconnect attempts will be made. |
Inspecting Incoming MessageEvent
When the server pushes an un-named or standard data: chunk, the onmessage callback receives a standard DOM MessageEvent:
eventSource.onmessage = function(event) {
console.log('Payload data string:', event.data);
console.log('Last Event ID:', event.lastEventId);
console.log('Server Origin:', event.origin);
// Parse JSON if server emits structured payloads
try {
const data = JSON.parse(event.data);
console.log('Parsed Object:', data);
} catch (e) {
console.warn('Payload was raw text, not JSON');
}
};
Key Differences: EventSource vs. fetch() Streaming
| Feature | EventSource |
fetch(url) + ReadableStream |
|---|---|---|
| API Simplicity | Very high (Event-driven, 3 lines of code) | Low (Manual buffer reading, chunk decoding) |
| Automatic Reconnection | Built-in natively by browser engine | None (Must write manual loop & retry backoff) |
| Event ID Resumption | Built-in via Last-Event-ID header |
Manual (Must track and send in request headers) |
| HTTP Methods | GET only |
GET, POST, PUT, PATCH, DELETE |
| Custom Request Headers | No custom headers (except cookies via withCredentials) |
Full custom header control (Authorization: Bearer ...) |
| Request Body | Cannot send request body | Can send JSON / Binary request body |
๐ป Interactive Code Playground
Below is a complete, interactive EventSource dashboard simulator. It allows you to create, observe, disconnect, and simulate server-side events and error drops while inspecting readyState transitions in real time.
Starter Code
Line-by-Line Code Breakdown
- Lines 105โ144: Implements a standard WHATWG compliant emulator tracking the 3 exact integer states:
0(CONNECTING),1(OPEN), and2(CLOSED). - Lines 149โ153:
new MockEventSource('/api/live-feed')initializes the connection in state0 (CONNECTING). - Lines 159โ162 (
onopen): Triggered as soon as the HTTP200 OKheader withtext/event-streamarrives. State transitions to1 (OPEN). - Lines 165โ167 (
onmessage): Fires every time a data packet arrives from the server. Extractsevent.dataandevent.lastEventId. - Lines 170โ173 (
onerror): Fires when the socket drops. Unlike traditional fetch calls that fail permanently,EventSourcetransitions back to0 (CONNECTING)and automatically attempts to reconnect. - Lines 191โ197 (
close()): Permanently terminates the connection, settingreadyState = 2 (CLOSED)and preventing any further reconnection attempts.
Expected Browser Render Output
โก Native EventSource API Lifecycle
Connection State: [ 1: OPEN (Green Pill) ]
[ 1. Initialize (Disabled) ] [ 2. Simulate Server Push ] [ 3. Simulate Drop ] [ 4. close() Stream ]
Terminal Output:
[10:14:02 PM] Creating new EventSource("/api/live-feed")...
[10:14:02 PM] โ onopen fired! readyState is now OPEN (1)
[10:14:05 PM] ๐ฅ onmessage received [ID: 1]: {"metric":"CPU_LOAD","value":"42.8%"}
[10:14:08 PM] โ ๏ธ onerror fired! Connection lost. readyState is now CONNECTING (0). Auto-reconnecting...
[10:14:10 PM] โ onopen fired! readyState is now OPEN (1)
[10:14:15 PM] ๐ eventSource.close() executed! Connection permanently terminated.๐๏ธ Hands-On Exercise
๐ฏ The Challenge: Build a Reconnecting ManagedEventSource Class
Instructions:
- Create a JavaScript class named
ManagedEventSourcethat wraps nativeEventSource. - Features to implement:
- Tracks reconnect attempt counts.
- Enforces a
maxRetriesthreshold (e.g. 5 retries). If exceeded, automatically calls.close()and fires anonFatalErrorcallback. - Provides a
.getStatus()method returning{ url, readyState, stateName, retryCount }.
- Provide a simple UI with buttons to trigger connection and view status.
๐ Starter Code Sandbox
โ ๏ธ Common Pitfalls
- Omitting
.close()on Component Unmount: In Single Page Applications (React, Vue, Svelte), failing to calleventSource.close()inside cleanup hooks (useEffectreturn oronUnmounted) leaves background SSE streams running forever, causing massive memory leaks and socket leaks. - Attempting to Set Custom Headers in
new EventSource(): The standardEventSourceInitdictionary only acceptswithCredentials: boolean. Passing{ headers: { 'Authorization': 'Bearer ...' } }is silently ignored by the browser. If custom headers are mandatory, use a polyfill or query parameters. - Treating
onerroras Fatal by Default: When the server restarts or a mobile device switches from Wi-Fi to 5G,onerrorfires, but the browser automatically reconnects. Do not tear down your entire UI on the firstonerrorevent; inspectreadyStatefirst.
๐ก Pro Tips
- Server-Side Termination via HTTP 204: If the server finishes emitting data (e.g. LLM completion done or batch export completed), the server can send an HTTP
204 No Contentstatus code. This signals the browser engine to close theEventSourcecleanly without retrying. - Detecting Tab Visibility (
document.visibilityState): To conserve mobile battery and backend server connections, pause or close SSE connections whendocument.visibilityState === 'hidden'and reconnect when the user switches back to the tab.
๐ Key Takeaways
EventSourceis the browser's built-in, lightweight JavaScript interface for receiving Server-Sent Events.readyStatehas 3 states:0(CONNECTING),1(OPEN), and2(CLOSED).onopentriggers when the stream is established;onmessagecaptures standard data messages;onerrorfires when a network drop or server error occurs.- The browser engine handles network drop retries automatically unless
.close()is explicitly called. - Always call
eventSource.close()when components unmount or pages transition to avoid background socket leakage. - --