跪拜 Guibai
← All articles
Frontend

Streaming AI Output in Vue 3: The ReadableStream Pipeline Every Frontend Dev Needs

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

Streaming is now table stakes for any AI product interface. A frontend engineer who can't wire up a `ReadableStream` and parse SSE will ship a chat UI that feels broken compared to every major AI assistant.

Summary

Streaming output is the standard for AI chat interfaces because it attacks perceived latency, not absolute latency. When a user sees text appear token by token, the psychological wait collapses even if the total generation time is unchanged. The mechanism relies on a simple protocol agreement—`stream: true` in the request body—and the Server-Sent Events (SSE) format, which delivers each token as a `data:` line over a persistent HTTP connection.

A Vue 3 implementation walks through the full pipeline: `response.body.getReader()` opens the stream, `TextDecoder` converts binary chunks to UTF-8, a buffer reassembles lines that were split across network packets, and a filter isolates SSE `data:` lines. Each line's JSON payload is parsed to extract `delta.content`, which gets appended to a reactive `ref`. Vue's reactivity system then updates the DOM automatically.

The piece also contrasts SSE with WebSocket, noting that SSE's built-in browser reconnection and one-way server-to-client flow make it the right fit for AI streaming. Practical additions include `AbortController` for canceling in-flight requests, error handling for non-OK responses, and a blinking cursor to signal that generation is still active.

Takeaways
Streaming output reduces perceived waiting time by showing tokens immediately, not by making the model generate faster.
Set `stream: true` in the request body to tell the LLM server to send tokens as they are produced rather than batching them.
SSE data arrives as `data:` lines separated by blank lines; the stream ends with `data: [DONE]`.
`response.body.getReader()` returns a reader that pulls binary chunks from a `ReadableStream`.
`TextDecoder` converts `Uint8Array` chunks into UTF-8 strings for parsing.
A buffer variable is necessary because network chunks can split an SSE line in half.
Filter lines that start with `data:`, strip the prefix, parse the JSON, and extract `choices[0].delta.content`.
Appending each token to a Vue `ref` triggers reactive DOM updates, creating the typewriter effect.
SSE is one-way (server to client) with built-in browser reconnection; WebSocket is bidirectional but requires manual reconnect logic.
Use `AbortController` to cancel an in-flight streaming request when the user submits a new prompt.
Set `content.value` to a placeholder like 'Thinking...' before the fetch to give immediate feedback.
A blinking CSS cursor during loading signals that generation is still in progress.
Conclusions

Streaming's value is almost entirely psychological—it doesn't speed up the model, but it makes the product feel alive.

The buffer pattern for reassembling split SSE lines is a microcosm of a universal problem in network programming: chunks are not messages.

SSE's built-in auto-reconnect is an underappreciated advantage over WebSocket for read-heavy AI interfaces; it removes an entire class of state-management bugs.

Placing 'Thinking...' before the fetch call is a small UX detail that acknowledges the user's action immediately, preventing the perception that the app is frozen.

Teaching `TextEncoder`/`TextDecoder` alongside the streaming pipeline grounds the abstraction in concrete bytes, which helps developers debug garbled output.

Concepts & terms
SSE (Server-Sent Events)
A one-way HTTP-based protocol where the server pushes a stream of `data:` lines to the client. Each message is separated by a blank line, and the stream ends with `data: [DONE]`. Browsers support automatic reconnection.
ReadableStream
A Web API representing a stream of binary data. `response.body` in a fetch call is a ReadableStream. Its `getReader()` method returns a reader that pulls chunks via `read()`, which returns `{ value: Uint8Array, done: boolean }`.
TextDecoder
A Web API that decodes a `Uint8Array` (binary bytes) into a string using a specified encoding, typically UTF-8. The inverse is `TextEncoder`, which encodes a string into bytes.
Buffer (in streaming context)
A temporary string that holds an incomplete SSE line that was split across two network chunks. The next chunk is prepended with the buffer contents before parsing, ensuring no partial lines are processed.
AbortController
A Web API that provides an `abort()` method and a `signal` property. Passing the signal to `fetch` allows the request to be cancelled programmatically, freeing resources when a user navigates away or submits a new prompt.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗