Start · Sprachen · JavaScript · Referenz · ReadableStreamBYOBReader

ReadableStreamBYOBReader

Schnittstelle

Leser für einen byte-orientierten <code>ReadableStream</code>, der Daten direkt in einen vom Aufrufer bereitgestellten Puffer schreibt („Bring Your Own Buffer“) und so Kopien vermeidet.

seit JavaScript Baseline 2024: Chrome 89, Firefox 102, Safari 18.4 Kategorie: webapi

Signatur

interface ReadableStreamBYOBReader

Beschreibung

ReadableStreamBYOBReader ist neben ReadableStreamDefaultReader der zweite Lesertyp der Streams-API. „BYOB“ steht für Bring Your Own Buffer: Der Aufrufer übergibt bei jedem read() eine eigene ArrayBufferView (z. B. Uint8Array). Die Quelle schreibt die Bytes direkt in diesen Speicher. So werden Kopien und das Anlegen neuer Puffer vermieden.

Ein BYOB-Leser funktioniert nur mit Byte-Streams. Das sind Streams, die mit new ReadableStream({ type: "bytes", ... }) erzeugt wurden, oder z. B. Response.body bei fetch(). Man erhält den Leser über stream.getReader({ mode: "byob" }) oder mit new ReadableStreamBYOBReader(stream). Danach ist der Stream gesperrt, bis releaseLock() aufgerufen wird.

Wichtige Mitglieder:

  • constructor(stream): erzeugt einen Leser und sperrt den Stream.
  • read(view, options?): gibt ein Promise<{ value, done }> zurück. value ist eine neue View auf denselben Speicher mit den gelesenen Bytes. Ab Chrome 120 bzw. ES-Streams-Spezifikation gibt es die Option { min }: Das Promise wird erst erfüllt, wenn mindestens so viele Elemente gelesen wurden.
  • releaseLock(): gibt die Sperre auf den Stream frei.
  • cancel(reason?): bricht den Stream ab.
  • closed: Promise, das erfüllt wird, wenn der Stream geschlossen ist. Bei einem Fehler oder nach dem Freigeben der Sperre wird es abgelehnt.

Der Leser lohnt sich vor allem beim Parsen von Binärformaten, bei großen Datenmengen und wenn genau n Bytes in einen fest angelegten Puffer gelesen werden sollen, etwa für Dateiheader oder Netzwerkprotokolle.

Parameter

Name Typ Default Beschreibung
$stream Pflicht ReadableStream Nur Konstruktor: der zu lesende Byte-Stream (type: "bytes"). Ist er kein Byte-Stream oder bereits gesperrt, wird ein TypeError geworfen.
$view Pflicht ArrayBufferView Für read(): Zielpuffer, z. B. Uint8Array oder DataView. Er muss eine Länge größer 0 haben. Der zugrunde liegende ArrayBuffer wird dabei übertragen (detached).
$options.min number 1 Für read(): Mindestanzahl der Elemente, bevor das Promise erfüllt wird. Wird der Stream vorher geschlossen, kommen auch weniger Elemente zurück.
$reason any undefined Für cancel(): Abbruchgrund, der an die Quelle weitergereicht wird.

Rückgabewert

Typ
ReadableStreamBYOBReader
Beschreibung
Der Konstruktor bzw. getReader({ mode: "byob" }) liefert den Leser. read() liefert Promise<{ value: ArrayBufferView | undefined, done: boolean }>.

Beispiele

Leser erzeugen und einen Byte-Stream lesen

const stream = new ReadableStream({
  type: "bytes",
  start(controller) {
    controller.enqueue(new Uint8Array([1, 2, 3, 4, 5]));
    controller.close();
  }
});

const reader = stream.getReader({ mode: "byob" });
// alternativ: new ReadableStreamBYOBReader(stream)

let buffer = new ArrayBuffer(8);
const { value, done } = await reader.read(new Uint8Array(buffer));
console.log(value, done);
console.log(buffer.byteLength); // alter Puffer ist übertragen
buffer = value.buffer;         // Speicher weiterverwenden
Uint8Array(5) [1, 2, 3, 4, 5] false 0

Gesamten fetch-Body in einen wiederverwendeten Puffer lesen

const readAllBytes = async (url) => {
  const response = await fetch(url);
  const reader = response.body.getReader({ mode: "byob" });
  let buffer = new ArrayBuffer(64 * 1024);
  let total = 0;

  try {
    while (true) {
      const { value, done } = await reader.read(new Uint8Array(buffer));
      if (done) break;
      total += value.byteLength;
      // value hier verarbeiten ...
      buffer = value.buffer; // Speicher für den nächsten Lesevorgang zurückholen
    }
  } finally {
    reader.releaseLock();
  }
  return total;
};

console.log(`Gelesen: ${await readAllBytes("/daten.bin")} Bytes`);
Gelesen: 123456 Bytes

Genau n Bytes lesen, z. B. einen Dateiheader

const readExactly = async (reader, n) => {
  let buffer = new ArrayBuffer(n);
  let offset = 0;
  while (offset < n) {
    const { value, done } = await reader.read(
      new Uint8Array(buffer, offset, n - offset)
    );
    if (done) throw new Error("Stream endete vorzeitig");
    buffer = value.buffer;
    offset += value.byteLength;
  }
  return new Uint8Array(buffer);
};

const res = await fetch("/bild.png");
const reader = res.body.getReader({ mode: "byob" });
const header = await readExactly(reader, 8);
console.log([...header].map((b) => b.toString(16)).join(" "));
await reader.cancel("Header reicht");

// In neueren Umgebungen kürzer: await reader.read(new Uint8Array(8), { min: 8 })
89 50 4e 47 d a 1a a

// Wichtig · Fallstricke

  • Übertragener Puffer: Nach read(view) ist der ursprüngliche ArrayBuffer übertragen (byteLength === 0). Arbeite immer mit value bzw. value.buffer weiter. Ein Zugriff auf die alte View ergibt leere Daten.
  • Nur Byte-Streams: getReader({ mode: \"byob\" }) auf einem normalen Stream wirft einen TypeError. Mit view.byteLength === 0 wird das Promise abgelehnt.
  • Gelesene Menge: read() kann weniger Bytes liefern als der Puffer fasst. Prüfe deshalb immer value.byteLength oder nutze die Option { min }. Diese ist neuer und wird noch nicht überall unterstützt.
  • Bei done: true ist value eine leere View auf den Puffer oder undefined, wenn der Stream abgebrochen wurde.
  • Gibt es noch offene read()-Aufrufe, führt releaseLock() dazu, dass diese abgelehnt werden (nach aktueller Spezifikation).
  • Kompatibilität: Chrome/Edge ab 89 und Firefox ab 102. Safari unterstützt Byte-Streams und BYOB-Leser erst seit 18.4. In Node.js ist der Leser über stream/web bzw. global verfügbar. Für ältere Umgebungen gibt es eine Feature-Erkennung mit typeof ReadableStreamBYOBReader !== \"undefined\" und als Ausweichlösung den ReadableStreamDefaultReader.