[ Web Proxy ]
URL:
Viewing: https://developer.mozilla.org/de/docs/Web/API/Streams_API/Using_readable_byte_streams [Back]  [Original]

Verwendung von lesbaren Bytestreams - Web-APIs | MDN

Dieser Inhalt wurde automatisch aus dem Englischen bersetzt, und kann Fehler enthalten. Erfahre mehr ber dieses Experiment.

View in English Always switch to English

Verwendung von lesbaren Bytestreams

Lesbare Bytestreams sind lesbare Streams, die eine zugrunde liegende Bytequelle mit type: "bytes" haben und die effiziente Zero-Copy-bertragung von Daten von der zugrunde liegenden Quelle zu einem Verbraucher untersttzen (unter Umgehung der internen Warteschlangen des Streams). Sie sind fr Anwendungsflle gedacht, bei denen Daten in willkrlich groen und potenziell sehr groen Blcken bereitgestellt oder angefordert werden knnen und daher das Vermeiden von Kopien wahrscheinlich die Effizienz verbessert.

Dieser Artikel erklrt, wie sich lesbare Bytestreams von normalen "Standard"-Streams unterscheiden und wie Sie sie erstellen und konsumieren.

Hinweis: Lesbare Bytestreams sind fast identisch zu "normalen" lesbaren Streams und fast alle Konzepte sind gleich. Dieser Artikel geht davon aus, dass Sie diese Konzepte bereits verstehen, und deckt sie nur oberflchlich ab (wenn berhaupt). Wenn Sie mit den relevanten Konzepten nicht vertraut sind, lesen Sie bitte zuerst: Verwendung von lesbaren Streams, Streams-Konzepte und Nutzungsbersicht und Streams API Konzepte.

In diesem Artikel

bersicht

Lesbare Streams bieten eine konsistente Schnittstelle zum Streamen von Daten von einer zugrunde liegenden Quelle, wie einer Datei oder einem Socket, zu einem Verbraucher, wie einem Leser, einem Transformstream oder einem beschreibbaren Stream. In einem normalen lesbaren Stream passieren die Daten von der zugrunde liegenden Quelle immer ber die internen Warteschlangen zu einem Verbraucher. Ein lesbarer Bytestream unterscheidet sich dadurch, dass, wenn die internen Warteschlangen leer sind, die zugrunde liegende Quelle direkt an den Verbraucher schreiben kann (eine effiziente Zero-Copy-bertragung).

Ein lesbarer Bytestream wird erstellt, indem type: "bytes" im underlyingSource-Objekt angegeben wird, das als erster Parameter an den ReadableStream()-Konstruktor bergeben werden kann. Mit diesem Wert wird der Stream mit einem ReadableByteStreamController erstellt, und dies ist das Objekt, das an die zugrunde liegende Quelle bergeben wird, wenn die start(controller)- und pull(controller)-Rckruffunktionen aufgerufen werden.

Der Hauptunterschied zwischen ReadableByteStreamController und dem Standard-Controller (ReadableStreamDefaultController) besteht darin, dass er eine zustzliche Eigenschaft ReadableByteStreamController.byobRequest vom Typ ReadableStreamBYOBRequest hat. Dies reprsentiert eine ausstehende Leseanforderung eines Verbrauchers, die als Zero-Copy-bertragung von der zugrunde liegenden Quelle ausgefhrt wird. Die Eigenschaft wird null, wenn keine ausstehende Anfrage vorliegt.

Eine byobRequest wird nur verfgbar gemacht, wenn eine Leseanforderung an einem lesbaren Bytestream gestellt wird und keine Daten in den internen Warteschlangen des Streams vorhanden sind (wenn Daten vorhanden sind, wird die Anfrage aus diesen Warteschlangen erfllt).

Eine zugrunde liegende Bytequelle, die Daten bertragen muss, muss die byobRequest-Eigenschaft berprfen und, wenn sie verfgbar ist, sie verwenden, um Daten zu bertragen. Wenn die Eigenschaft null ist, sollten eingehende Daten stattdessen den internen Warteschlangen des Streams hinzugefgt werden, indem ReadableByteStreamController.enqueue() benutzt wird (dies ist der einzige Weg, Daten zu bertragen, wenn ein "Standard"-Stream verwendet wird).

Der ReadableStreamBYOBRequest hat eine view-Eigenschaft, die eine Ansicht auf den fr die bertragung zugewiesenen Puffer ist. Daten von einer zugrunde liegenden Quelle sollten in diese Eigenschaft geschrieben werden, und dann muss die zugrunde liegende Quelle respond() aufrufen und angeben, wie viele Bytes geschrieben wurden. Dies signalisiert, dass die Daten bertragen werden sollen, und die ausstehende Leseanforderung des Verbrauchers wird erfllt. Nach dem Aufruf von respond() kann die view nicht mehr beschrieben werden.

Es gibt auch eine zustzliche Methode ReadableStreamBYOBRequest.respondWithNewView(), an die eine zugrunde liegende Quelle eine "neue" Ansicht mit zu bertragenden Daten bergeben kann. Diese neue Ansicht muss ber denselben Speicherpuffer wie das Original und ab demselben Startversatz liegen. Diese Methode knnte verwendet werden, wenn die zugrunde liegende Bytequelle zuerst die Ansicht an einen Worker-Thread zur Befllung bertragen muss (zum Beispiel) und sie dann zurckholt, bevor sie auf die byobRequest antwortet. In den meisten Fllen wird diese Methode nicht bentigt.

Lesbare Bytestreams werden normalerweise mit einem ReadableStreamBYOBReader gelesen, der durch Aufrufen von ReadableStream.getReader() am Stream erhalten werden kann, indem mode: "byob" im Optionsparameter angegeben wird.

Ein lesbarer Bytestream kann auch mit einem Standard-Reader (ReadableStreamDefaultReader) gelesen werden, aber in diesem Fall werden byobRequest-Objekte nur erstellt, wenn die automatische Pufferzuweisung fr den Stream aktiviert ist (autoAllocateChunkSize wurde fr das underlyingSource des Streams eingestellt). Beachten Sie, dass die Gre, die durch autoAllocateChunkSize angegeben wird, in diesem Fall fr die Puffergre verwendet wird; fr einen Byte-Reader wird der verwendete Puffer vom Verbraucher bereitgestellt. Wenn die Eigenschaft nicht angegeben wurde, wird der Standard-Reader immer noch "funktionieren", aber die zugrunde liegende Quelle wird nie ein byobRequest angeboten, und alle Daten werden durch die internen Warteschlangen des Streams bertragen.

Abgesehen von den oben genannten Unterschieden sind der Controller und die zugrunde liegende Quelle fr Byte-Streams sehr hnlich zu denen fr Standard-Streams, und werden auf hnliche Weise verwendet.

Beispiele

Zugrunde liegende Push-Quelle mit Byte-Reader

Dieses Live-Beispiel zeigt, wie ein lesbarer Bytestream mit einer Push-zugrunde liegenden Bytequelle erstellt und mit einem Byte-Reader gelesen wird.

Im Gegensatz zu einer ziehenden zugrunde liegenden Bytequelle knnen Daten jederzeit eintreffen. Daher muss die zugrunde liegende Quelle controller.byobRequest verwenden, um eingehende Daten zu bertragen, falls vorhanden, und andernfalls die Daten in die internen Warteschlangen des Streams einreihen. Da die Daten jederzeit eintreffen knnen, wird das berwachungsverhalten in der underlyingSource.start()-Rckruffunktion eingerichtet.

Das Beispiel ist stark von einem Push-Byte-Quellenbeispiel in der Stream-Spezifikation beeinflusst. Es verwendet eine simulierte "hypothetische Socket"-Quelle, die Daten beliebiger Gren bereitstellt. Der Leser wird absichtlich an verschiedenen Stellen verzgert, um es der zugrunde liegenden Quelle zu ermglichen, sowohl bertragung als auch Einreihung zu verwenden, um Daten an den Stream zu senden. Der Rckstau-Support wird nicht demonstriert.

Hinweis: Eine zugrunde liegende Bytequelle kann auch mit einem Standard-Reader verwendet werden. Wenn die automatische Pufferzuweisung aktiviert ist, liefert der Controller Puffer fester Gre fr Zero-Copy-bertragungen, wenn eine ausstehende Anfrage eines Lesers vorhanden ist und die internen Warteschlangen des Streams leer sind. Wenn die automatische Pufferzuweisung nicht aktiviert ist, werden alle Daten des Bytestreams immer eingeordnet. Dies ist hnlich dem Verhalten, das in den "Pull: Unterliegende Byte-Quellenbeispiele gezeigt wird.

Simulierte zugrunde liegende Socket-Quelle

Die simulierte zugrunde liegende Quelle hat drei wichtige Methoden:

  • select2() reprsentiert eine ausstehende Anfrage an den Socket. Sie gibt ein Promise zurck, das aufgelst wird, wenn Daten verfgbar sind.
  • readInto() liest Daten aus dem Socket in einen bereitgestellten Puffer und lscht dann die Daten.
  • close() schliet den Socket.

Die Implementierung ist sehr einfach. Wie unten gezeigt, erstellt select2() einen zufllig groen Puffer mit zuflligen Daten bei einem Timeout. Die erstellten Daten werden dann in einen Puffer gelesen und in readInto() gelscht.

js
class MockHypotheticalSocket {
  constructor() {
    this.max_data = 800; // total amount of data to stream from "socket"
    this.max_per_read = 100; // max data per read
    this.min_per_read = 40; // min data per read
    this.data_read = 0; // total data read so far (capped is maxdata)
    this.socketData = null;
  }

  // Method returning promise when this socket is readable.
  select2() {
    // Object used to resolve promise
    const resultObj = {};
    resultObj["bytesRead"] = 0;

    return new Promise((resolve /*, reject */) => {
      if (this.data_read >= this.max_data) {
        // Out of data
        resolve(resultObj);
        return;
      }

      // Emulate slow read of data
      setTimeout(() => {
        const numberBytesReceived = this.getNumberRandomBytesSocket();
        this.data_read += numberBytesReceived;
        this.socketData = this.randomByteArray(numberBytesReceived);
        resultObj["bytesRead"] = numberBytesReceived;
        resolve(resultObj);
      }, 500);
    });
  }

  /* Read data into specified buffer offset */
  readInto(buffer, offset, length) {
    let dataLength = 0;
    if (this.socketData) {
      dataLength = this.socketData.length;
      const myView = new Uint8Array(buffer, offset, length);
      // Write the length of data specified into buffer
      // Code assumes buffer always bigger than incoming data
      for (let i = 0; i < dataLength; i++) {
        myView[i] = this.socketData[i];
      }
      this.socketData = null; // Clear "socket" data after reading
    }
    return dataLength;
  }

  // Dummy close function
  close() {}

  // Return random number bytes in this call of socket
  getNumberRandomBytesSocket() {
    // Capped to remaining data and the max min return-per-read range
    const remainingData = this.max_data - this.data_read;
    const numberBytesReceived =
      remainingData < this.min_per_read
        ? remainingData
        : this.getRandomIntInclusive(
            this.min_per_read,
            Math.min(this.max_per_read, remainingData),
          );
    return numberBytesReceived;
  }

  // Return random number between two values
  getRandomIntInclusive(min, max) {
    min = Math.ceil(min);
    max = Math.floor(max);
    return Math.floor(Math.random() * (max - min + 1) + min);
  }

  // Return random character string
  randomChars(length = 8) {
    let string = "";
    let choices =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()";

    for (let i = 0; i < length; i++) {
      string += choices.charAt(Math.floor(Math.random() * choices.length));
    }
    return string;
  }

  /* Return random Uint8Array of bytes */
  randomByteArray(bytes = 8) {
    const textEncoder = new TextEncoder();
    return textEncoder.encode(this.randomChars(bytes));
  }
}

Erstellen eines lesbaren Socket-Push-Bytestreams

Der folgende Code zeigt, wie ein lesbarer Socket-"Push"-Bytestream definiert wird.

Die Definition des underlyingSource-Objekts wird als erster Parameter an den ReadableStream()-Konstruktor bergeben. Um dies zu einem lesbaren "Byte"-Stream zu machen, geben wir type: "bytes" als Eigenschaft des Objekts an. Dies stellt sicher, dass dem Stream ein ReadableByteStreamController (anstelle des Standard-Controllers (ReadableStreamDefaultController)) bergeben wird.

Da Daten am Socket eintreffen knnen, bevor der Verbraucher bereit ist, sie zu verarbeiten, wird alles, was das Lesen der zugrunde liegenden Quelle betrifft, in der start()-Rckruffunktion konfiguriert (wir warten nicht auf einen Pull, um mit der Verarbeitung von Daten zu beginnen). Die Implementierung ffnet den "Socket" und ruft select2() auf, um Daten anzufordern. Wenn das zurckgegebene Promise aufgelst wird, berprft der Code, ob controller.byobRequest existiert (nicht null ist) und ruft in diesem Fall socket.readInto() auf, um Daten in die Anfrage zu kopieren und sie zu bertragen. Wenn byobRequest nicht existiert, gibt es keine ausstehende Anfrage eines konsumierenden Streams, die als Zero-Copy-bertragung erfllt werden kann. In diesem Fall wird controller.enqueue() verwendet, um Daten in die internen Warteschlangen des Streams zu kopieren.

Die select2()-Anfrage fr mehr Daten wird erneut gepostet, bis eine Anfrage ohne Daten zurckgegeben wird. An diesem Punkt wird der Controller verwendet, um den Stream zu schlieen.

js
const stream = makeSocketStream("dummy host", "dummy port");

const DEFAULT_CHUNK_SIZE = 400;

function makeSocketStream(host, port) {
  const socket = new MockHypotheticalSocket();

  return new ReadableStream({
    type: "bytes",

    start(controller) {
      readRepeatedly().catch((e) => controller.error(e));
      function readRepeatedly() {
        return socket.select2().then(() => {
          // Since the socket can become readable even when there's
          // no pending BYOB requests, we need to handle both cases.
          let bytesRead;
          if (controller.byobRequest) {
            const v = controller.byobRequest.view;
            bytesRead = socket.readInto(v.buffer, v.byteOffset, v.byteLength);
            if (bytesRead === 0) {
              controller.close();
            }
            controller.byobRequest.respond(bytesRead);
            logSource(`byobRequest with ${bytesRead} bytes`);
          } else {
            const buffer = new ArrayBuffer(DEFAULT_CHUNK_SIZE);
            bytesRead = socket.readInto(buffer, 0, DEFAULT_CHUNK_SIZE);
            if (bytesRead === 0) {
              controller.close();
            } else {
              controller.enqueue(new Uint8Array(buffer, 0, bytesRead));
            }
            logSource(`enqueue() ${bytesRead} bytes (no byobRequest)`);
          }

          if (bytesRead === 0) {
            return;
            // no more bytes in source
          }
          return readRepeatedly();
        });
      }
    },

    cancel() {
      socket.close();
      logSource(`cancel(): socket closed`);
    },
  });
}

Beachten Sie, dass readRepeatedly() ein Promise zurckgibt, und wir verwenden dies, um Fehler beim Einrichten oder Handhaben der Leseoperation abzufangen. Die Fehler werden dann an den Controller weitergegeben, wie oben gezeigt (siehe readRepeatedly().catch((e) => controller.error(e));).

Am Ende wird eine cancel()-Methode bereitgestellt, um die zugrunde liegende Quelle zu schlieen; die pull()-Rckruffunktion wird nicht bentigt und ist daher nicht implementiert.

Konsumieren des Push-Bytestreams

Der folgende Code erstellt einen ReadableStreamBYOBReader fr den Socket-Bytestream und verwendet ihn, um Daten in einen Puffer zu lesen. Beachten Sie, dass processText() rekursiv aufgerufen wird, um mehr Daten zu lesen, bis der Puffer gefllt ist. Wenn die zugrunde liegende Quelle signalisiert, dass keine Daten mehr vorhanden sind, wird reader.read() done auf wahr gesetzt und damit die Leseoperation abgeschlossen.

Dieser Code ist fast genau derselbe wie fr das Beispiel Zugrunde liegende Pull-Quelle mit Byte-Reader. Der einzige Unterschied besteht darin, dass der Leser einige Codezeilen enthlt, um das Lesen zu verlangsamen, sodass die Protokollausgabe zeigen kann, dass Daten eingereiht werden, wenn sie nicht schnell genug gelesen werden.

js
const reader = stream.getReader({ mode: "byob" });
let buffer = new ArrayBuffer(4000);
readStream(reader);

function readStream(reader) {
  let bytesReceived = 0;
  let offset = 0;

  while (offset < buffer.byteLength) {
    // read() returns a promise that resolves when a value has been received
    reader
      .read(new Uint8Array(buffer, offset, buffer.byteLength - offset))
      .then(async function processText({ done, value }) {
        // Result objects contain two properties:
        // done  - true if the stream has already given all its data.
        // value - some data. Always undefined when done is true.

        if (done) {
          logConsumer(`readStream() complete. Total bytes: ${bytesReceived}`);
          return;
        }

        buffer = value.buffer;
        offset += value.byteLength;
        bytesReceived += value.byteLength;

        // logConsumer(`Read ${bytesReceived} bytes: ${value}`);
        logConsumer(`Read ${bytesReceived} bytes`);
        result += value;

        // Add delay to emulate when data can't be read and data is enqueued
        if (bytesReceived > 300 && bytesReceived < 600) {
          logConsumer(`Delaying read to emulate slow stream reading`);
          const delay = (ms) =>
            new Promise((resolve) => {
              setTimeout(resolve, ms);
            });
          await delay(1000);
        }

        // Read some more, and call this function again
        return reader
          .read(new Uint8Array(buffer, offset, buffer.byteLength - offset))
          .then(processText);
      });
  }
}

Abbrechen des Streams mit dem Reader

Wir knnen ReadableStreamBYOBReader.cancel() verwenden, um den Stream abzubrechen. Fr dieses Beispiel rufen wir die Methode auf, wenn eine Schaltflche mit dem Grund "Benutzerwahl" geklickt wird (anderer HTML-Code und Code fr die Schaltflche nicht gezeigt). Wir loggen auch, wenn der Abbruchvorgang abgeschlossen ist.

js
button.addEventListener("click", () => {
  reader
    .cancel("user choice")
    .then(() => logConsumer("reader.cancel complete"));
});

ReadableStreamBYOBReader.releaseLock() kann verwendet werden, um den Reader freizugeben, ohne den Stream abzubrechen. Beachten Sie jedoch, dass alle ausstehenden Leseanforderungen sofort abgelehnt werden. Ein neuer Leser kann spter erworben werden, um die verbleibenden Blcke zu lesen.

berwachung des Streams auf Schlieen/Fehler

Die ReadableStreamBYOBReader.closed-Eigenschaft gibt ein Promise zurck, das aufgelst wird, wenn der Stream geschlossen ist, und abgelehnt wird, wenn ein Fehler auftritt. Whrend in diesem Fall keine Fehler erwartet werden, sollte der folgende Code den Abschlussfall protokollieren.

js
reader.closed
  .then(() => {
    logConsumer("ReadableStreamBYOBReader.closed: resolved");
  })
  .catch(() => {
    logConsumer("ReadableStreamBYOBReader.closed: rejected:");
  });

Ergebnis

Das Protokollieren von der zugrunde liegenden Push-Quelle (links) und dem Verbraucher (rechts) wird unten gezeigt. Beachten Sie den Zeitraum in der Mitte, in dem Daten eingereiht anstatt als Zero-Copy-Operation bertragen wurden.

Zugrunde liegende Pull-Quelle mit Byte-Reader

Dieses Live-Beispiel zeigt, wie Daten von einer "Pull"-zugrunde liegenden Bytequelle, wie einer Datei, gelesen und von einem Stream als Zero-Copy-bertragung an einen ReadableStreamBYOBReader bertragen werden knnen.

Simulierte zugrunde liegende Datei-Quelle

Fr die zugrunde liegende Pull-Quelle verwenden wir die folgende Klasse, um sehr oberflchlich einen Node.js FileHandle zu simulieren, insbesondere die read()-Methode. Die Klasse generiert Zufallsdaten, um eine Datei zu reprsentieren. Die read()-Methode liest einen "halbzuflligen" groen Block zuflliger Daten in einen bereitgestellten Puffer von der angegebenen Position. Die close()-Methode tut nichts: Sie wird nur bereitgestellt, um zu zeigen, wo Sie die Quelle schlieen knnten, wenn Sie den Konstruktor fr den Stream definieren.

Hinweis: Eine hnliche Klasse wird fr alle "Pull-Quellen"-Beispiele verwendet. Sie wird hier nur zur Information gezeigt (so dass offensichtlich ist, dass es sich um einen Mock handelt).

js
class MockUnderlyingFileHandle {
  constructor() {
    this.maxdata = 100; // "file size"
    this.maxReadChunk = 25; // "max read chunk size"
    this.minReadChunk = 13; // "min read chunk size"
    this.filedata = this.randomByteArray(this.maxdata);
    this.position = 0;
  }

  // Read data from "file" at position/length into specified buffer offset
  read(buffer, offset, length, position) {
    // Object used to resolve promise
    const resultObj = {};
    resultObj["buffer"] = buffer;
    resultObj["bytesRead"] = 0;

    return new Promise((resolve /*, reject */) => {
      if (position >= this.maxdata) {
        // Out of data
        resolve(resultObj);
        return;
      }

      // Simulate a file read that returns random numbers of bytes
      // Read minimum of bytes requested and random bytes that can be returned
      let readLength =
        Math.floor(
          Math.random() * (this.maxReadChunk - this.minReadChunk + 1),
        ) + this.minReadChunk;
      readLength = length > readLength ? readLength : length;

      // Read random data into supplied buffer
      const myView = new Uint8Array(buffer, offset, readLength);
      // Write the length of data specified
      for (let i = 0; i < readLength; i++) {
        myView[i] = this.filedata[position + i];
        resultObj["bytesRead"] = i + 1;
        if (position + i + 1 >= this.maxdata) {
          break;
        }
      }
      // Emulate slow read of data
      setTimeout(() => {
        resolve(resultObj);
      }, 1000);
    });
  }

  // Dummy close function
  close() {}

  // Return random character string
  randomChars(length = 8) {
    let string = "";
    let choices =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()";

    for (let i = 0; i < length; i++) {
      string += choices.charAt(Math.floor(Math.random() * choices.length));
    }
    return string;
  }

  // Return random Uint8Array of bytes
  randomByteArray(bytes = 8) {
    const textEncoder = new TextEncoder();
    return textEncoder.encode(this.randomChars(bytes));
  }
}

Erstellen eines lesbaren Datei-Bytestreams

Der folgende Code zeigt, wie ein lesbarer Datei-Bytestream definiert wird.

Genau wie im vorherigen Beispiel wird die Definition des underlyingSource-Objekts als erster Parameter an den ReadableStream()-Konstruktor bergeben. Um dies zu einem lesbaren "Byte"-Stream zu machen, geben wir type: "bytes" als Eigenschaft des Objekts an. Dies stellt sicher, dass dem Stream ein ReadableByteStreamController bergeben wird.

Die start()-Funktion ffnet einfach den Datei-Handle, der dann in der cancel()-Rckruffunktion geschlossen wird. cancel() wird bereitgestellt, um alle Ressourcen aufzurumen, wenn ReadableStream.cancel() oder ReadableStreamDefaultController.close() aufgerufen werden.

Der interessanteste Code befindet sich in der pull()-Rckruffunktion. Diese kopiert Daten von der Datei in die ausstehende Leseanforderung (ReadableByteStreamController.byobRequest) und ruft dann respond() auf, um anzugeben, wie viele Daten im Puffer sind und sie zu bertragen. Wenn 0 Bytes von der Datei bertragen wurden, wissen wir, dass alles kopiert wurde, und rufen close() am Controller auf, was wiederum dazu fhrt, dass cancel() an der zugrunde liegenden Quelle aufgerufen wird.

js
const stream = makeReadableByteFileStream("dummy file.txt");

function makeReadableByteFileStream(filename) {
  let fileHandle;
  let position = 0;
  return new ReadableStream({
    type: "bytes", // An underlying byte stream!
    start(controller) {
      // Called to initialize the underlying source.
      // For a file source open a file handle (here we just create the mocked object).
      fileHandle = new MockUnderlyingFileHandle();
      logSource(
        `start(): ${controller.constructor.name}.byobRequest = ${controller.byobRequest}`,
      );
    },
    async pull(controller) {
      // Called when there is a pull request for data
      const theView = controller.byobRequest.view;
      const { bytesRead, buffer } = await fileHandle.read(
        theView.buffer,
        theView.byteOffset,
        theView.byteLength,
        position,
      );
      if (bytesRead === 0) {
        await fileHandle.close();
        controller.close();
        controller.byobRequest.respond(0);
        logSource(
          `pull() with byobRequest. Close controller (read bytes: ${bytesRead})`,
        );
      } else {
        position += bytesRead;
        controller.byobRequest.respond(bytesRead);
        logSource(`pull() with byobRequest. Transfer ${bytesRead} bytes`);
      }
    },
    cancel(reason) {
      // This is called if the stream is cancelled (via reader or controller).
      // Clean up any resources
      fileHandle.close();
      logSource(`cancel() with reason: ${reason}`);
    },
  });
}

Konsumieren des Bytestreams

Der folgende Code erstellt einen ReadableStreamBYOBReader fr den Datei-Bytestream und verwendet ihn, um Daten in einen Puffer zu lesen. Beachten Sie, dass processText() rekursiv aufgerufen wird, um mehr Daten zu lesen, bis der Puffer gefllt ist. Wenn die zugrunde liegende Quelle signalisiert, dass keine Daten mehr vorhanden sind, wird reader.read() done auf wahr gesetzt und damit die Leseoperation abgeschlossen.

js
const reader = stream.getReader({ mode: "byob" });
let buffer = new ArrayBuffer(200);
readStream(reader);

function readStream(reader) {
  let bytesReceived = 0;
  let offset = 0;

  // read() returns a promise that resolves when a value has been received
  reader
    .read(new Uint8Array(buffer, offset, buffer.byteLength - offset))
    .then(function processText({ done, value }) {
      // Result objects contain two properties:
      // done  - true if the stream has already given all its data.
      // value - some data. Always undefined when done is true.

      if (done) {
        logConsumer(`readStream() complete. Total bytes: ${bytesReceived}`);
        return;
      }

      buffer = value.buffer;
      offset += value.byteLength;
      bytesReceived += value.byteLength;

      logConsumer(
        `Read ${value.byteLength} (${bytesReceived}) bytes: ${value}`,
      );
      result += value;

      // Read some more, and call this function again
      return reader
        .read(new Uint8Array(buffer, offset, buffer.byteLength - offset))
        .then(processText);
    });
}

Zuletzt fgen wir einen Handler hinzu, der den Stream abbricht, wenn eine Schaltflche geklickt wird (anderer HTML-Code und Code fr die Schaltflche nicht gezeigt).

js
button.addEventListener("click", () => {
  reader.cancel("user choice").then(() => {
    logConsumer(`reader.cancel complete`);
  });
});

Ergebnis

Das Protokollieren von der zugrunde liegenden Pull-Quelle (links) und dem Verbraucher (rechts) wird unten gezeigt. Besonders hervorzuheben ist, dass die:

  • start()-Funktion einen ReadableByteStreamController erhlt
  • der an den Leser bergebene Puffer gro genug ist, um die gesamte "Datei" zu umfassen. Die zugrunde liegende Datenquelle liefert die Daten in zufallsgegroen Blcken.

Zugrunde liegende Pull-Quelle mit Standardleser

Dieses Live-Beispiel zeigt, wie dieselben Daten als Zero-Copy-bertragung mit einem Standardleser (ReadableStreamDefaultReader) gelesen werden knnen. Dies verwendet dieselbe simulierte zugrunde liegende Datei-Quelle wie im vorherigen Beispiel.

Erstellen eines lesbaren Datei-Bytestreams mit automatischer Pufferzuweisung

Der einzige Unterschied in unserer zugrunde liegenden Quelle besteht darin, dass wir autoAllocateChunkSize angeben mssen und dass die Gre als Ansichts-Puffergre fr controller.byobRequest verwendet wird, anstatt eine vom Verbraucher bereitgestellte.

js
const DEFAULT_CHUNK_SIZE = 20;
const stream = makeReadableByteFileStream("dummy file.txt");

function makeReadableByteFileStream(filename) {
  let fileHandle;
  let position = 0;
  return new ReadableStream({
    type: "bytes", // An underlying byte stream!
    start(controller) {
      // Called to initialize the underlying source.
      // For a file source open a file handle (here we just create the mocked object).
      fileHandle = new MockUnderlyingFileHandle();
      logSource(
        `start(): ${controller.constructor.name}.byobRequest = ${controller.byobRequest}`,
      );
    },
    async pull(controller) {
      // Called when there is a pull request for data
      const theView = controller.byobRequest.view;
      const { bytesRead, buffer } = await fileHandle.read(
        theView.buffer,
        theView.byteOffset,
        theView.byteLength,
        position,
      );
      if (bytesRead === 0) {
        await fileHandle.close();
        controller.close();
        controller.byobRequest.respond(0);
        logSource(
          `pull() with byobRequest. Close controller (read bytes: ${bytesRead})`,
        );
      } else {
        position += bytesRead;
        controller.byobRequest.respond(bytesRead);
        logSource(`pull() with byobRequest. Transfer ${bytesRead} bytes`);
      }
    },
    cancel(reason) {
      // This is called if the stream is cancelled (via reader or controller).
      // Clean up any resources
      fileHandle.close();
      logSource(`cancel() with reason: ${reason}`);
    },
    autoAllocateChunkSize: DEFAULT_CHUNK_SIZE, // Only relevant if using a default reader
  });
}

Konsumieren des Bytestreams mit Standardleser

Der folgende Code erstellt einen ReadableStreamDefaultReader fr den Datei-Bytestream, indem stream.getReader(); ohne Angabe des Modus aufgerufen wird, und verwendet ihn, um Daten in einen Puffer zu lesen. Der Code funktioniert genauso wie im vorherigen Beispiel, auer dass der Puffer vom Stream anstatt vom Verbraucher bereitgestellt wird.

js
const reader = stream.getReader();
readStream(reader);

function readStream(reader) {
  let bytesReceived = 0;
  let result = "";

  // read() returns a promise that resolves
  // when a value has been received
  reader.read().then(function processText({ done, value }) {
    // Result objects contain two properties:
    // done  - true if the stream has already given you all its data.
    // value - some data. Always undefined when done is true.
    if (done) {
      logConsumer(`readStream() complete. Total bytes: ${bytesReceived}`);
      return;
    }

    bytesReceived += value.length;
    logConsumer(
      `Read ${value.length} (${bytesReceived}). Current bytes = ${value}`,
    );
    result += value;

    // Read some more, and call this function again
    return reader.read().then(processText);
  });
}

Zuletzt fgen wir einen Handler hinzu, der den Stream abbricht, wenn eine Schaltflche geklickt wird (anderer HTML-Code und Code fr die Schaltflche nicht gezeigt).

js
button.addEventListener("click", () => {
  reader.cancel("user choice").then(() => {
    logConsumer(`reader.cancel complete`);
  });
});

Ergebnis

Das Protokollieren von der zugrunde liegenden Byte-Pull-Quelle (links) und dem Verbraucher (rechts) wird unten gezeigt.

Beachten Sie, dass die Blcke jetzt hchstens 20-Byte breit sind, da dies die Gre des automatisch zugewiesenen Puffers ist, der in der zugrunde liegenden Byte-Quelle (autoAllocateChunkSize) angegeben wurde. Diese werden als Zero-Copy-bertragungen ausgefhrt.

Zugrunde liegende Pull-Quelle mit Standardleser und ohne Zuordnung

Der Vollstndigkeit halber knnen wir auch einen Standardleser mit einer Bytequelle verwenden, die keine automatische Pufferzuordnung untersttzt.

In diesem Fall wird der Controller jedoch kein byobRequest zur Verfgung stellen, in das die zugrunde liegende Quelle schreiben kann. Stattdessen msste die zugrunde liegende Quelle die Daten einreihen. Beachten Sie unten, dass wir in pull() prfen mssen, ob der byobRequest existiert, um dieses Szenario zu untersttzen.

js
const stream = makeReadableByteFileStream("dummy file.txt");
const DEFAULT_CHUNK_SIZE = 40;

function makeReadableByteFileStream(filename) {
  let fileHandle;
  let position = 0;
  return new ReadableStream({
    type: "bytes", // An underlying byte stream!
    start(controller) {
      // Called to initialize the underlying source.
      // For a file source open a file handle (here we just create the mocked object).
      fileHandle = new MockUnderlyingFileHandle();
      logSource(
        `start(): ${controller.constructor.name}.byobRequest = ${controller.byobRequest}`,
      );
    },
    async pull(controller) {
      // Called when there is a pull request for data
      if (controller.byobRequest) {
        const theView = controller.byobRequest.view;
        const { bytesRead, buffer } = await fileHandle.read(
          theView.buffer,
          theView.byteOffset,
          theView.byteLength,
          position,
        );
        if (bytesRead === 0) {
          await fileHandle.close();
          controller.close();
          controller.byobRequest.respond(0);
          logSource(
            `pull() with byobRequest. Close controller (read bytes: ${bytesRead})`,
          );
        } else {
          position += bytesRead;
          controller.byobRequest.respond(bytesRead);
          logSource(`pull() with byobRequest. Transfer ${bytesRead} bytes`);
        }
      } else {
        // No BYOBRequest so enqueue data to stream
        // NOTE, this branch would only execute for a default reader if autoAllocateChunkSize is not defined.
        const myNewBuffer = new Uint8Array(DEFAULT_CHUNK_SIZE);
        const { bytesRead, buffer } = await fileHandle.read(
          myNewBuffer.buffer,
          myNewBuffer.byteOffset,
          myNewBuffer.byteLength,
          position,
        );
        if (bytesRead === 0) {
          await fileHandle.close();
          controller.close();
          controller.enqueue(myNewBuffer);
          logSource(
            `pull() with no byobRequest. Close controller (read bytes: ${bytesRead})`,
          );
        } else {
          position += bytesRead;
          controller.enqueue(myNewBuffer);
          logSource(`pull() with no byobRequest. enqueue() ${bytesRead} bytes`);
        }
      }
    },
    cancel(reason) {
      // This is called if the stream is cancelled (via reader or controller).
      // Clean up any resources
      fileHandle.close();
      logSource(`cancel() with reason: ${reason}`);
    },
  });
}

Ergebnis

Das Protokollieren von der zugrunde liegenden Pull-Quelle (links) und den Verbraucher (rechts) wird unten gezeigt. Beachten Sie, dass auf der Seite der zugrunde liegenden Quelle angezeigt wird, dass die Daten eingereiht wurden, anstatt als Zero-Byte-bertragung bertragen zu werden.

Siehe auch


Web Proxy Viewer  |  New URL  |  Original Page