Stream API - Readable Streams
A ReadableStream is a source of chunks. You either pull chunks with a reader or push them through a pipe. Response bodies are the most common readable streams you will encounter.
Getting a reader
const response = await fetch('/api/events/live');
const reader = response.body.getReader();
try {
while (true) {
const { value, done } = await reader.read();
if (done) break;
handleChunk(value);
}
} finally {
reader.releaseLock();
}
Creating a readable stream
function counterStream(limit) {
let current = 0;
return new ReadableStream({
pull(controller) {
if (current >= limit) {
controller.close();
return;
}
controller.enqueue(current++);
}
});
}
for await (const value of counterStream(3)) {
console.log(value); // 0, 1, 2
}
pull() is called whenever the consumer wants more data, which keeps production paced to consumption.
Pushing data with a controller
let controllerRef;
const stream = new ReadableStream({
start(controller) {
controllerRef = controller;
}
});
function pushMessage(text) {
controllerRef.enqueue(new TextEncoder().encode(text));
}
function finish() {
controllerRef.close();
}
This pattern suits event sources such as chat messages or server-sent updates.
Cancellation
const stream = new ReadableStream({
pull(controller) {
// ...
},
cancel(reason) {
console.log('Consumer cancelled:', reason);
cleanup();
}
});
Cancellation runs when the consumer aborts or stops reading. Use it to release sockets, timers, or file handles.
Teeing a stream
const response = await fetch('/api/large.csv');
const [forParsing, forBackup] = response.body.tee();
parseCsv(forParsing);
saveToDisk(forBackup);
tee() splits a stream into two independent branches. Both branches must be consumed, or backpressure from the slower one slows the source.
Async iteration
for await (const chunk of response.body) {
view.append(chunk);
}
Async iteration is a concise alternative to a manual reader loop in browsers that support it.
Back to course overview · Previous: Stream API - Basics · Next: Stream API - Writable Streams