Skip to content
Dashboard

An Introduction to Streaming on the Web

Staff Developer Advocate

Chunking data, handling backpressure, server-side events, and more.

Comparison between a non-streaming and streaming HTTP response Comparison between a non-streaming and streaming HTTP response
Comparison between a non-streaming and streaming HTTP response

Link to headingChunks

const decoder = new TextDecoder();
const encoder = new TextEncoder();
const readableStream = new ReadableStream({
start(controller) {
const text = "Stream me!";
controller.enqueue(encoder.encode(text));
controller.close();
},
});
const transformStream = new TransformStream({
transform(chunk, controller) {
const text = decoder.decode(chunk);
controller.enqueue(encoder.encode(text.toUpperCase()));
},
});
const writableStream = new WritableStream({
write(chunk) {
console.log(decoder.decode(chunk));
},
});
readableStream
.pipeThrough(transformStream)
.pipeTo(writableStream); // STREAM ME!

const decoder = new TextDecoder();
const response = await fetch('/api/stream');
const reader = response.body.getReader();
let done = false;
while (!done) {
const { value, done: doneReading } = await reader.read();
done = doneReading;
const data = JSON.parse(decoder.decode(value));
// Do something with data
}

Link to headingHandling backpressure

Link to headingHandling backpressure with web streams

const stream = new WritableStream(...)
async function writeData(data) {
const writer = stream.getWriter();
for (const chunk of data) {
// Wait for the ready promise to resolve before writing the next chunk
await writer.ready;
writer.write(chunk);
}
writer.close();
}

Link to headingServer-Sent Events

import { createParser } from "eventsource-parser";
export function OpenAITextStream(
res: Response,
): ReadableStream {
const encoder = new TextEncoder()
const decoder = new TextDecoder()
let counter = 0
const stream = new ReadableStream({
async start(controller): Promise<void> {
function onParse(event: ParsedEvent | ReconnectInterval): void {
if (event.type === 'event') {
const data = event.data
if (data === '[DONE]') {
controller.close()
return
}
try {
const json = JSON.parse(data)
const text =
json.choices[0]?.delta?.content ?? json.choices[0]?.text ?? ''
if (counter < 2 && (text.match(/\n/) || []).length) {
return
}
const queue = encoder.encode(`${JSON.stringify(text)}\n`)
controller.enqueue(queue)
counter++
} catch (e) {
controller.error(e)
}
}
}
const parser = createParser(onParse)
// [Asynchronously iterate](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of) the response's body
for await (const chunk of res.body as any) {
parser.feed(decoder.decode(chunk))
}
}
})
return stream
}

Link to headingStreaming on Vercel

export const config = {
runtime: "edge",
};
const delay = (ms) => new Promise((res) => setTimeout(res, ms));
export default async function handler() {
const encoder = new TextEncoder();
const readable = new ReadableStream({
async start(controller) {
controller.enqueue(encoder.encode("<html><body>"));
await delay(500);
controller.enqueue(encoder.encode("<ul><li>List Item 1</li>"));
await delay(500);
controller.enqueue(encoder.encode("<li>List Item 2</li>"));
await delay(500);
controller.enqueue(encoder.encode("<li>List Item 3</li></ul>"));
await delay(500);
controller.enqueue(encoder.encode("</body></html>"));
controller.close();
},
});
return new Response(readable, {
headers: { "Content-Type": "text/html; charset=utf-8" },
});
}

Link to headingVercel AI SDK

app/api/chat/route.ts
import { OpenAIStream, StreamingTextResponse } from 'ai'
export const runtime = 'edge'
export async function POST(req: Request) {
const response = await openai.createChatCompletion({
model: 'gpt-3.5-turbo',
stream: true,
messages: [...]
})
// Convert the response into a friendly text-stream
const stream = OpenAIStream(response)
// Respond with the stream
return new StreamingTextResponse(stream)
}

'use client'
import { useChat } from 'ai'
export default function Chat() {
const { messages, input, handleInputChange, handleSubmit } = useChat()
return ...
}

Link to headingConclusion