Streams für Webentwickler erklärt
Wenn Sie fetch() aufrufen und auf eine Antwort warten, hat der Browser diese Daten bereits in Teilen empfangen. Die Web Streams API gibt Ihrem JavaScript-Code Zugriff auf diese Teile, sobald sie eintreffen, anstatt zu warten, bis die gesamte Antwort vorliegt, bevor Sie darauf zugreifen können.
Diese Verschiebung – von „auf alles warten” zu „während des Eintreffens verarbeiten” – ist das Wesen von Streams.
Wichtigste Erkenntnisse
- Die Web Streams API ermöglicht es Ihnen, Daten inkrementell zu verarbeiten, während sie eintreffen, anstatt vollständige Antworten im Speicher zu puffern.
ReadableStream,WritableStreamundTransformStreamsind die drei zentralen Primitive – zusammensetzbare Bausteine für Daten-Pipelines.response.bodyvonfetch()ist der häufigste Einstiegspunkt: einReadableStream, den Sie Stück für Stück lesen können.- Verwenden Sie
pipeThrough()undpipeTo(), um Transformationen und Ausgaben miteinander zu verketten, mit automatischer Backpressure-Behandlung.
Warum das vollständige Laden auf einmal ein Problem ist
Der traditionelle Ansatz zum Abrufen von Daten sieht so aus:
const response = await fetch('/large-dataset.json')
const data = await response.json()
// Nothing happens until all bytes are downloaded and parsed
Für kleine Datenmengen ist das in Ordnung. Bei einer 50 MB großen JSON-Datei oder einer lang laufenden API-Antwort halten Sie jedoch das gesamte Objekt im Speicher, bevor Sie einen einzigen Datensatz verarbeiten. Auf Geräten mit begrenzten Ressourcen oder bei langsamen Verbindungen bedeutet das träge Benutzeroberflächen, hohen Speicherdruck und frustrierte Benutzer.
Streams ermöglichen es Ihnen, mit Daten zu arbeiten, sobald der erste Chunk eintrifft.
Die drei zentralen Primitive der Web Streams API
Die Web Streams API basiert auf drei Klassen:
ReadableStream– eine Quelle, aus der Sie Daten lesenWritableStream– ein Ziel, in das Sie Daten schreibenTransformStream– sitzt dazwischen, liest von einer Seite und schreibt transformierte Daten auf die andere
Daten bewegen sich durch diese Streams in Chunks – kleinen Teilen, die nacheinander verarbeitet werden. Ein Chunk kann ein Uint8Array von Bytes, ein String oder ein beliebiger JavaScript-Wert sein, je nach Stream.
Fetch-Streaming: Inkrementelles Lesen einer Antwort
Die meisten fetch()-Antworten stellen ihren Body als ReadableStream über response.body zur Verfügung. Dies ist der häufigste Einstiegspunkt in JavaScript-Streams für Frontend-Entwickler.
async function processLargeResponse(url) {
const response = await fetch(url)
const reader = response.body.getReader()
const decoder = new TextDecoder()
try {
while (true) {
const { done, value } = await reader.read()
if (done) break
console.log(decoder.decode(value, { stream: true }))
}
} finally {
reader.releaseLock()
}
}
reader.read() gibt ein Promise zurück, das mit { value, done } aufgelöst wird. Wenn done true ist, ist der Stream beendet. Dieses Muster ermöglicht es Ihnen, eine mehrmegabyte-große Antwort Chunk für Chunk zu verarbeiten, ohne das Ganze zu puffern.
Hinweis zu Streaming-Request-Bodies: Das Übergeben eines
ReadableStreamalsfetch()-Request-Body ist möglich, hat aber uneinheitliche Browser-Unterstützung. Streaming-Antworten sind das gut unterstützte, praktische Muster, auf das Sie heute setzen sollten.
Discover how at OpenReplay.com.
Aufbau von Daten-Pipelines mit pipeThrough() und pipeTo()
Wirklich mächtig werden Streams durch Komposition. Sie können einen ReadableStream durch eine oder mehrere TransformStream-Instanzen leiten und das Ergebnis in einen WritableStream pipen.
fetch('./data.txt').then((response) =>
response.body
.pipeThrough(new TextDecoderStream())
.pipeThrough(new TransformStream({
transform(chunk, controller) {
controller.enqueue(chunk.toUpperCase())
}
}))
.pipeTo(new WritableStream({
write(chunk) {
document.body.textContent += chunk
}
}))
)
Diese Pipeline dekodiert Bytes zu Text, transformiert jeden Chunk in Großbuchstaben und schreibt ihn dann ins DOM – alles inkrementell, ohne auf die vollständige Antwort zu warten.
pipeThrough() verbindet einen ReadableStream mit einem TransformStream und gibt einen neuen ReadableStream zurück. pipeTo() verbindet einen ReadableStream mit einem WritableStream und gibt ein Promise zurück, das aufgelöst wird, wenn der Stream abgeschlossen ist.
Backpressure: Wie Streams Überlastung vermeiden
Wenn ein Consumer Daten langsamer verarbeitet als ein Producer sie erzeugt, wenden Streams Backpressure an – ein Signal, das sich durch die Pipe-Kette zurück ausbreitet und der Quelle mitteilt, langsamer zu werden. Dies geschieht automatisch, wenn Sie pipeTo() und pipeThrough() verwenden. Das ist einer der Hauptgründe, Piping dem manuellen Lesen von Chunks in einer Schleife vorzuziehen.
Integrierte Streams, die Sie kennen sollten
Der Browser liefert mehrere fertige Stream-Utilities mit:
TextDecoderStream/TextEncoderStream– konvertieren zwischen Bytes und StringsCompressionStream/DecompressionStream– komprimieren oder dekomprimieren Daten on-the-fly mit gzip oder deflateBlob.stream()– liest jedenBloboder jedeFilealsReadableStream
Modernes Node.js unterstützt ebenfalls die Web Streams API, sodass Pipelines, die Sie für den Browser erstellen, sauber in serverseitige Umgebungen übertragen werden können.
Fazit
Die Web Streams API bietet Frontend-Entwicklern eine zusammensetzbare, speichereffiziente Möglichkeit, Daten zu verarbeiten, die im Laufe der Zeit eintreffen. ReadableStream und TransformStream sind die Primitive, die Sie am häufigsten verwenden werden – besonders in Kombination mit fetch() für die inkrementelle Antwortverarbeitung. Beginnen Sie mit response.body, greifen Sie zu pipeThrough(), wenn Sie Daten transformieren müssen, und lassen Sie Backpressure die Flusskontrolle für Sie übernehmen.
FAQs
Ja. ReadableStream, WritableStream, TransformStream und die Piping-Methoden werden in allen modernen Browsern unterstützt, einschließlich Chrome, Firefox, Safari und Edge. Das Streaming von Fetch-Antwort-Bodies über response.body wird ebenfalls weitgehend unterstützt. Streaming-Request-Bodies mit fetch haben eingeschränktere Unterstützung, überprüfen Sie daher Kompatibilitätstabellen, bevor Sie sich auf diese Funktion verlassen.
Wenn eine Stufe in einer Pipe-Kette einen Fehler auslöst, breitet sich der Fehler durch die Pipeline aus. Die lesbare Seite wird fehlerhaft und die schreibbare Seite wird abgebrochen. Sie können dies behandeln, indem Sie ein Options-Objekt mit einem Signal übergeben oder indem Sie das von pipeTo zurückgegebene Promise abfangen. Bei manuellen Leseschleifen umschließen Sie Ihre read-Aufrufe mit try-catch-Blöcken.
Node.js lieferte ursprünglich seine eigene Stream-API mit Readable-, Writable- und Transform-Klassen. Die Web Streams API ist ein separater Standard, der für Browser entwickelt wurde. Moderne Versionen von Node.js unterstützen beide. Die Web Streams API verwendet ein Pull-basiertes Modell mit Promises, während klassische Node-Streams ein ereignisbasiertes Push-Modell verwenden. Code, der gegen die Web Streams API geschrieben wurde, ist portabel zwischen Browser- und Server-Umgebungen.
Wenn die Antwort klein ist, sagen wir unter ein paar hundert Kilobyte, ist das Puffern mit response.json oder response.text einfacher und völlig effizient. Streams bieten Mehrwert beim Umgang mit großen Datenmengen, Echtzeitdaten oder Situationen, in denen Sie Teilergebnisse anzeigen möchten, bevor die vollständige Antwort eintrifft. Für einfache API-Aufrufe, die kompaktes JSON zurückgeben, ist der traditionelle Ansatz in Ordnung.
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.