Signatur
Beschreibung
Mit cancel() teilt ein Konsument mit, dass er an den weiteren Daten eines Byte-Streams kein Interesse mehr hat. Der Aufruf leitet den Abbruch an die Quelle weiter: Bei einem selbst erzeugten ReadableStream mit type: "bytes" wird der cancel(reason)-Callback der zugrunde liegenden Quelle aufgerufen, bei einem fetch()-Body wird die Netzwerkverbindung abgebaut.
Der Aufruf wirkt genauso wie ReadableStream.prototype.cancel(), nur eben über den Reader, der den Stream gerade gesperrt hält. Das ist praktisch, weil ein gesperrter Stream sich nicht direkt über stream.cancel() abbrechen lässt. Das schlägt mit einem TypeError fehl. Solange der Reader die Sperre hält, führt der Abbruch also über ihn.
Nach dem Abbruch wird jede noch offene read()-Anfrage mit { done: true, value: undefined } abgeschlossen. Pufferinhalte in der Warteschlange werden verworfen. Das zurückgegebene Promise wird mit undefined erfüllt, sobald die Quelle ihren Abbruch fertig bearbeitet hat. Es wird abgelehnt, wenn der Stream bereits in einem Fehlerzustand ist oder die cancel-Logik der Quelle eine Ausnahme wirft.
Typische Einsätze sind: Lesen beenden, sobald genug Bytes da sind (zum Beispiel nur den Dateikopf auslesen), Abbruch durch Benutzer oder Zeitlimit und Aufräumen im Fehlerfall.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $reason | any | undefined | Optionaler Grund für den Abbruch, zum Beispiel ein String oder ein Error. Er wird an den cancel-Callback der zugrunde liegenden Quelle weitergereicht und ist nur für die Quelle bestimmt. |
Rückgabewert
undefined erfüllt wird, sobald der Stream abgebrochen ist. Es wird mit einem TypeError abgelehnt, wenn der Reader nicht mehr aktiv ist (nach releaseLock()), und mit dem gespeicherten Fehler, wenn der Stream sich im Fehlerzustand befindet.Beispiele
Nur den Dateikopf einer Antwort lesen und dann abbrechen
// Liest die ersten 8 Bytes (z. B. die PNG-Signatur) und bricht den Download ab
const readHeader = async (url, size = 8) => {
const response = await fetch(url);
const reader = response.body.getReader({ mode: 'byob' });
let buffer = new ArrayBuffer(size);
let offset = 0;
while (offset < size) {
const { value, done } = await reader.read(
new Uint8Array(buffer, offset, size - offset)
);
if (done) break;
buffer = value.buffer; // Puffer wurde übertragen, neu übernehmen
offset += value.byteLength;
}
// Restliche Daten werden nicht gebraucht, also Verbindung schließen
await reader.cancel('Header gelesen');
return new Uint8Array(buffer, 0, offset);
};
const header = await readHeader('/bilder/logo.png');
console.log([...header].map((b) => b.toString(16).padStart(2, '0')).join(' '));
Grund des Abbruchs in einer eigenen Byte-Quelle empfangen
const stream = new ReadableStream({
type: 'bytes',
pull(controller) {
controller.enqueue(new Uint8Array([1, 2, 3, 4]));
},
cancel(reason) {
console.log('Quelle abgebrochen:', reason);
}
});
const reader = stream.getReader({ mode: 'byob' });
const { value } = await reader.read(new Uint8Array(4));
console.log('Gelesen:', value);
await reader.cancel('Benutzer hat abgebrochen');
console.log('Abbruch abgeschlossen');
// Nach dem Abbruch meldet read() sofort das Ende
const result = await reader.read(new Uint8Array(4));
console.log(result.done);
Lesen mit Zeitlimit abbrechen
const readWithTimeout = async (stream, ms) => {
const reader = stream.getReader({ mode: 'byob' });
const timer = setTimeout(() => {
reader.cancel(new Error(`Zeitlimit von ${ms} ms überschritten`));
}, ms);
let total = 0;
let buffer = new ArrayBuffer(4096);
try {
while (true) {
const { value, done } = await reader.read(new Uint8Array(buffer));
if (done) break;
total += value.byteLength;
buffer = value.buffer;
}
} finally {
clearTimeout(timer);
reader.releaseLock();
}
return total;
};
const res = await fetch('/api/grosse-datei');
console.log('Bytes gelesen:', await readWithTimeout(res.body, 2000));
// Wichtig · Fallstricke
- Nur für Byte-Streams: Einen
ReadableStreamBYOBReadergibt es nur für Streams mittype: "bytes"(zum Beispielfetch-Bodies oderBlob.stream()). Bei anderen Streams wirftgetReader({ mode: 'byob' })einenTypeError. - Abbruch ist endgültig: Ein abgebrochener Stream lässt sich nicht fortsetzen. Wer die Daten später noch braucht, sollte vorher mit
stream.tee()verzweigen. - Kein Fehler für Leser:
cancel()versetzt den Stream in den Zustand „geschlossen“, nicht in den Fehlerzustand. Offeneread()-Aufrufe liefern alsodone: trueund werden nicht abgelehnt. Derreasonkommt beim Leser nicht an. - Nach
releaseLock(): Ein Reader, dessen Sperre freigegeben wurde, kann nicht mehr abbrechen. Das Promise wird dann mitTypeErrorabgelehnt. In dem Fall stattdessenstream.cancel()verwenden. - Übertragene Puffer: Beim BYOB-Lesen werden die übergebenen
ArrayBufferübertragen (detached). Nach einem Abbruch sollte man nicht mehr auf alte Views zugreifen. - Kompatibilität: Safari unterstützt BYOB-Reader erst seit Version 18.4. Bei Bedarf per
typeof ReadableStreamBYOBReader !== 'undefined'prüfen und aufReadableStreamDefaultReaderzurückfallen. In Node.js sind die Web-Streams übernode:stream/webbzw. global verfügbar.