Start · Sprachen · JavaScript · Referenz · new WritableStream()

new WritableStream()

Methode

Erzeugt einen neuen <code>WritableStream</code>, der geschriebene Datenblöcke (Chunks) an eine selbst definierte Senke weitergibt – mit eingebautem Gegendruck (Backpressure).

seit JavaScript Baseline: alle modernen Browser (seit 2022 vollstä Kategorie: webapi

Signatur

new WritableStream(underlyingSink, queuingStrategy)

Beschreibung

Der Konstruktor new WritableStream() erzeugt einen beschreibbaren Stream der Streams API. Du legst über das Objekt underlyingSink fest, was mit jedem geschriebenen Chunk passieren soll. Das kann zum Beispiel sein: in eine Datei schreiben, über einen WebSocket senden, in einen Puffer sammeln oder im DOM anzeigen. Der Stream sorgt dabei selbst für Warteschlange, Reihenfolge und Fehlerweitergabe.

Geschrieben wird meist nicht direkt, sondern über einen WritableStreamDefaultWriter, den du mit stream.getWriter() bekommst, oder über readableStream.pipeTo(writable). Gibt deine write()-Methode ein Promise zurück, wartet der Stream, bis es erfüllt ist, bevor er den nächsten Chunk liefert. So entsteht der Gegendruck: writer.ready und writer.desiredSize zeigen dem Produzenten, wann er langsamer werden sollte.

Mit dem zweiten Argument queuingStrategy steuerst du, wie viele Daten gepuffert werden, bevor Gegendruck entsteht. Dafür eignen sich CountQueuingStrategy (nach Anzahl der Chunks) oder ByteLengthQueuingStrategy (nach Bytes).

Parameter

Name Typ Default Beschreibung
$underlyingSink Object {} Objekt mit optionalen Methoden:
  • start(controller): wird sofort beim Erzeugen aufgerufen, z. B. um eine Ressource zu öffnen. Darf ein Promise zurückgeben.
  • write(chunk, controller): wird für jeden Chunk aufgerufen. Gibt sie ein Promise zurück, wartet der Stream darauf.
  • close(controller): wird aufgerufen, nachdem alle Chunks geschrieben wurden und der Stream geschlossen wird.
  • abort(reason): wird beim Abbruch über writer.abort() bzw. stream.abort() aufgerufen.
controller ist ein WritableStreamDefaultController mit error(e) und signal (AbortSignal).
$queuingStrategy Object { highWaterMark: 1 } Strategie für die Warteschlange: highWaterMark (nicht negative Zahl, ab der Gegendruck entsteht) und optional size(chunk) (liefert die Größe eines Chunks). Alternativ eine Instanz von CountQueuingStrategy oder ByteLengthQueuingStrategy.

Beispiele

Einfacher Stream, der Chunks sammelt

const chunks = [];

const sink = new WritableStream({
  write(chunk) {
    chunks.push(chunk);
  },
  close() {
    console.log('Fertig:', chunks.join(' '));
  },
  abort(reason) {
    console.error('Abgebrochen:', reason);
  }
});

const writer = sink.getWriter();
await writer.write('Hallo');
await writer.write('Welt');
await writer.close();
Fertig: Hallo Welt

Fetch-Antwort per pipeTo verarbeiten und Bytes zählen

let bytes = 0;

const counter = new WritableStream({
  write(chunk) {
    // chunk ist ein Uint8Array
    bytes += chunk.byteLength;
  },
  close() {
    console.log(`Empfangen: ${bytes} Bytes`);
  }
});

const response = await fetch('https://example.com/');
await response.body.pipeTo(counter);
Empfangen: 1256 Bytes

Gegendruck mit asynchroner Senke und highWaterMark

const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

const slowSink = new WritableStream(
  {
    async write(chunk) {
      await sleep(100); // simuliert langsames Netzwerk
      console.log('geschrieben:', chunk);
    }
  },
  new CountQueuingStrategy({ highWaterMark: 2 })
);

const writer = slowSink.getWriter();
for (let i = 1; i <= 4; i++) {
  await writer.ready; // wartet, wenn die Warteschlange voll ist
  console.log('desiredSize:', writer.desiredSize);
  writer.write(i);
}
await writer.close();
desiredSize: 2 desiredSize: 1 desiredSize: 0 ... (Werte je nach Timing) geschrieben: 1 geschrieben: 2 geschrieben: 3 geschrieben: 4

Text dekodieren und ins DOM schreiben

const output = document.querySelector('#log');

const domSink = new WritableStream({
  write(text) {
    output.append(document.createTextNode(text));
  }
});

const res = await fetch('/api/log.txt');
await res.body
  .pipeThrough(new TextDecoderStream())
  .pipeTo(domSink);

// Wichtig · Fallstricke

  • Nur ein Schreiber: Solange ein Writer über getWriter() aktiv ist, ist der Stream gesperrt (locked === true). Mit writer.releaseLock() gibst du ihn wieder frei. pipeTo() sperrt den Stream ebenfalls.
  • Gegendruck beachten: Wer writer.write() ohne await writer.ready in einer Schleife aufruft, füllt die Warteschlange unbegrenzt und kann viel Speicher verbrauchen.
  • Fehler: Wirft write() einen Fehler oder gibt ein abgelehntes Promise zurück, geht der Stream dauerhaft in den Fehlerzustand. Alle folgenden Schreibversuche werden dann abgelehnt. Behandle abgelehnte Promises von write()/close(), sonst erscheinen unbehandelte Ablehnungen.
  • start() wird synchron im Konstruktor aufgerufen. Gibt es ein Promise zurück, wird write() erst aufgerufen, nachdem es erfüllt ist.
  • controller.signal ist erst seit etwa 2022 verfügbar (Chrome 98, Firefox 100, Safari 16.4). In älteren Umgebungen vorher prüfen.
  • Ein ungültiger highWaterMark (negativ oder NaN) führt zu einem RangeError.
  • In Node.js ist die Klasse ab Version 18 global verfügbar, davor über require('node:stream/web').