Client-side stream consumption
The server side streams events. Now you need a client that actually reads them as they arrive. curl with the right flag works for quick checks. Browsers need fetch with a ReadableStream reader because EventSource cannot send a POST body. Both patterns are short and reusable.
# -N disables buffering so you see events as they arrive
curl -N -X POST http://localhost:8000/sentiment/analyze/stream \
-H "Content-Type: application/json" \
-d '{"text":"Great product, terrible support.","tasks":["sentiment","emotion","summary"]}'The -N flag is mandatory. Without it, curl buffers the response and you see nothing until the stream closes, which defeats the whole purpose of streaming.
// EventSource only supports GET by default, so for POST bodies
// we use fetch with a ReadableStream reader.
async function streamAnalysis(text, tasks) {
const res = await fetch("http://localhost:8000/sentiment/analyze/stream", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text, tasks }),
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const events = buffer.split("\n\n");
buffer = events.pop() || "";
for (const raw of events) {
if (!raw.startsWith("data: ")) continue;
const event = JSON.parse(raw.slice(6));
handleEvent(event);
}
}
}
function handleEvent(event) {
switch (event.event) {
case "start": console.log("Tasks queued:", event.tasks); break;
case "task_start": console.log("Running:", event.task); break;
case "task_done": console.log("Done:", event.task, event.result); break;
case "task_error": console.warn("Failed:", event.task, event.error); break;
case "done": console.log("Stream complete"); break;
}
}Split on \n\n to find event boundaries. Keep the trailing partial chunk in a buffer for the next read. This is the standard pattern for consuming SSE from fetch when EventSource is not an option.
EventSource only supports GET and cannot send a request body. Your analyze/stream endpoint takes a JSON body with the text and task list, so you need POST. The fetch + ReadableStream approach is the standard workaround. If your use case fits GET with query parameters, EventSource gives you automatic reconnection and simpler code.