Start · Sprachen · JavaScript · Referenz · WritableStream

WritableStream

Schnittstelle

Standardisierte Abstraktion der Streams-API zum schrittweisen Schreiben von Daten (Chunks) an ein Ziel, die sogenannte Senke, inklusive Gegendruck-Steuerung.

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

Signatur

interface WritableStream

Beschreibung

WritableStream ist das Gegenstück zu ReadableStream: Statt Daten zu liefern, nimmt ein beschreibbarer Stream Daten in Stücken (Chunks) entgegen und gibt sie an eine Senke (underlying sink) weiter, etwa eine Datei, eine Netzwerkverbindung, einen Socket oder einfach ein Array im Speicher. Die Senke legst du über ein Objekt mit den Methoden start(), write(), close() und abort() fest.

Geschrieben wird nicht direkt auf den Stream, sondern über einen WritableStreamDefaultWriter, den du mit getWriter() erhältst. Der Writer sperrt den Stream (locked wird true), sodass nur ein Schreiber gleichzeitig aktiv ist. Über writer.ready und writer.desiredSize bekommst du den Gegendruck (backpressure) mit: Ist die interne Warteschlange voll, solltest du vor dem nächsten write() auf ready warten.

Typische Einsatzfälle sind readable.pipeTo(writable) zum Weiterleiten von Fetch-Antworten, die File System Access API (FileSystemWritableFileStream erbt von WritableStream), WebTransport, WebSockets-Streams sowie das Schreiben in TransformStream-Paare wie TextEncoderStream oder CompressionStream.

Wichtige Mitglieder: die Eigenschaft locked, die Methoden getWriter(), close() und abort(reason). Ein WritableStream ist außerdem ein übertragbares Objekt und kann per postMessage() an Worker weitergereicht werden.

Parameter

Name Typ Default Beschreibung
$underlyingSink object {} Definiert die Senke: start(controller) (Initialisierung, darf ein Promise liefern), write(chunk, controller) (verarbeitet einen Chunk, Promise signalisiert Abschluss), close(controller) (Abschluss nach allen Writes), abort(reason) (Abbruch). controller ist ein WritableStreamDefaultController mit error() und signal.
$queuingStrategy object | CountQueuingStrategy | ByteLengthQueuingStrategy { highWaterMark: 1 } Steuert den Gegendruck: highWaterMark (Füllgrenze der Warteschlange) und optional size(chunk) zur Bemessung eines Chunks.

Beispiele

Eigene Senke erstellen und per Writer beschreiben

const empfangen = [];

const stream = new WritableStream({
  start() {
    console.log('Senke bereit');
  },
  write(chunk) {
    empfangen.push(chunk);
  },
  close() {
    console.log('Fertig:', empfangen.join(' '));
  },
  abort(reason) {
    console.error('Abgebrochen:', reason);
  }
});

const writer = stream.getWriter();
const schreiben = async () => {
  for (const wort of ['Hallo', 'Streams', 'API']) {
    await writer.ready; // Gegendruck beachten
    await writer.write(wort);
  }
  await writer.close();
};

schreiben();
Senke bereit Fertig: Hallo Streams API

Fetch-Antwort per pipeTo() verarbeiten

const zaehleBytes = async (url) => {
  let bytes = 0;
  const response = await fetch(url);

  await response.body.pipeTo(new WritableStream({
    write(chunk) {
      bytes += chunk.byteLength; // chunk ist ein Uint8Array
    }
  }));

  return bytes;
};

zaehleBytes('https://example.com/').then((n) => console.log(`${n} Bytes empfangen`));
1256 Bytes empfangen

Text über TextEncoderStream in eine Senke schreiben

const log = new WritableStream({
  write(chunk) {
    console.log('Bytes:', chunk);
  }
});

const encoder = new TextEncoderStream();
const fertig = encoder.readable.pipeTo(log);

const writer = encoder.writable.getWriter();
await writer.write('Hi');
await writer.close();
await fertig;
console.log('gesperrt?', log.locked);
Bytes: Uint8Array(2) [72, 105] gesperrt? false

Gegendruck mit CountQueuingStrategy und Abbruch

const langsam = new WritableStream({
  write: (chunk) => new Promise((r) => setTimeout(() => {
    console.log('geschrieben', chunk);
    r();
  }, 100))
}, new CountQueuingStrategy({ highWaterMark: 2 }));

const writer = langsam.getWriter();
console.log('desiredSize:', writer.desiredSize);
writer.write(1);
writer.write(2);
console.log('desiredSize:', writer.desiredSize);

setTimeout(() => writer.abort('Benutzer hat abgebrochen'), 150);
desiredSize: 2 desiredSize: 0 geschrieben 1

// Wichtig · Fallstricke

  • Sperre: Solange ein Writer existiert, ist der Stream gesperrt. stream.close(), stream.abort() und ein zweites getWriter() werfen dann bzw. lehnen mit TypeError ab. Mit writer.releaseLock() gibst du die Sperre frei.
  • Gegendruck ignorieren: write() ohne await writer.ready puffert unbegrenzt im Speicher – bei großen Datenmengen ein Speicherproblem.
  • Fehlerbehandlung: Lehnt das Promise aus write() der Senke ab, wechselt der Stream in den Fehlerzustand; alle folgenden Writes schlagen fehl. Promises aus writer.write() immer mit await oder .catch() behandeln, sonst drohen unhandled rejections.
  • abort() verwirft noch wartende Chunks; close() wartet dagegen, bis alle geschriebenen Chunks verarbeitet sind.
  • Kompatibilität: In allen aktuellen Browsern verfügbar; Firefox unterstützt WritableStream erst seit Version 100. controller.signal ist neuer (ca. 2022). In Node.js global ab Version 18, davor über require('node:stream/web'). Die Übertragbarkeit per postMessage ist in Safari erst spät hinzugekommen.