跪拜 Guibai
← All articles
Vue.js · Frontend

Streaming AI Output from the Frontend: ReadableStream, SSE Parsing, and Vue 3

By forever_world ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Streaming is the baseline expectation for any AI chat product, and the frontend owns the entire perception of speed. Understanding the ReadableStream-to-SSE pipeline — not just calling a library — gives a developer direct control over latency perception, cancellation, and error recovery.

Summary

Large language models generate text token by token. Non-streaming APIs force the client to wait for the entire response before displaying anything, creating a dead-air gap that can stretch past 20 seconds. Streaming flips this: the server sends each token as it is produced, and the browser appends it to the UI immediately.

The frontend machinery that makes this work is compact. A fetch request with `stream: true` tells the LLM API to use chunked transfer encoding. The response body becomes a `ReadableStream`; a reader pulls binary chunks, a `TextDecoder` converts them to text, and a buffer reassembles lines split across network packets. The payload is Server-Sent Events (SSE) — lines prefixed with `data:` carrying JSON, terminated by a `[DONE]` sentinel.

A full Vue 3 implementation fits in roughly 40 lines. Reactive `ref()` variables hold the accumulating text, so the template updates automatically without manual DOM manipulation. The article also covers real-world traps: multi-byte character splitting, incomplete JSON lines, missing `[DONE]` handling, and using `AbortController` to cancel in-flight requests when a user resubmits.

Takeaways
Streaming output is HTTP chunked transfer: the server writes each token immediately instead of buffering the full response.
Setting `stream: true` in the fetch body to an OpenAI-compatible API switches it into streaming mode.
The browser reads the response with `response.body.getReader()`, which returns binary chunks via a `ReadableStream`.
A `TextDecoder` with `{ stream: true }` prevents garbled characters when a multi-byte UTF-8 character is split across chunks.
LLM streaming APIs use SSE format: lines starting with `data:` contain JSON payloads, and a `[DONE]` line signals the end.
A buffer is necessary to reassemble lines that a network packet may have cut in half.
Vue 3 reactivity means appending to a `ref()` variable automatically updates the DOM — no manual `innerHTML` calls.
An `AbortController` must cancel the previous fetch when a user sends a new prompt before the first one finishes.
Conclusions

Streaming is not a progressive enhancement; it is the difference between a product feeling broken and feeling responsive. A 27-second blank screen is interpreted as a crash.

The entire streaming pipeline — reader, decoder, buffer, SSE parser — is under 30 lines of vanilla JavaScript. The complexity is in the edge cases, not the happy path.

Vue's data-driven model aligns naturally with streaming: the only job is to concatenate tokens onto a reactive string. The framework handles the rendering diff.

Many frontend developers first encounter streaming through a library that hides the transport. Seeing the raw ReadableStream and SSE loop demystifies what the library is doing and makes debugging timeouts or partial renders straightforward.

Concepts & terms
ReadableStream
A Web API representing a stream of binary data that can be read incrementally. `response.body` in a fetch response is a ReadableStream; calling `.getReader()` returns a reader that yields chunks via `read()`.
SSE (Server-Sent Events)
A protocol where the server pushes a stream of text lines to the client over a single HTTP connection. LLM APIs use SSE to deliver tokens: each line begins with `data:` followed by a JSON payload, and the stream ends with `data: [DONE]`.
TextDecoder with stream option
`TextDecoder.decode(chunk, { stream: true })` tells the decoder that more bytes are coming, so it should not replace incomplete multi-byte characters (e.g., a Chinese character split across chunks) with a replacement character.
Reactive data (Vue ref)
A value wrapped in Vue's `ref()` that triggers automatic DOM updates when mutated. Appending to `content.value` inside a streaming loop causes the template to re-render without explicit DOM calls.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗