Signatur
Beschreibung
Sobald ein Stream mit stream.getReader() einen Reader bekommt, ist er gesperrt (stream.locked === true). Solange die Sperre besteht, kann niemand sonst aus dem Stream lesen: Weitere getReader()-Aufrufe, pipeTo(), pipeThrough() oder tee() lösen einen TypeError aus. Mit releaseLock() gibt der Reader den Stream wieder frei.
Das braucht man vor allem, wenn ein Stream teilweise gelesen und danach weitergereicht werden soll, z. B. um einen Dateikopf oder die ersten Bytes einer fetch()-Antwort zu prüfen und den Rest dann an eine andere Funktion oder eine Pipe-Kette zu übergeben. Daten, die noch nicht gelesen wurden, bleiben im Stream erhalten. Der nächste Reader macht genau dort weiter.
Nach dem Freigeben ist der Reader nicht mehr nutzbar: Weitere read()-Aufrufe werden mit einem TypeError abgelehnt, und das Promise reader.closed wird ebenfalls mit einem TypeError abgelehnt. Der Stream selbst wird durch releaseLock() nicht geschlossen oder abgebrochen. Wer den Stream beenden will, nutzt reader.cancel().
Laufen beim Aufruf noch offene read()-Anfragen, werden deren Promises nach aktueller Spezifikation mit einem TypeError abgelehnt. Ist der Reader schon freigegeben, macht ein erneuter Aufruf nichts.
Rückgabewert
stream.locked === false.Beispiele
Kopfzeile lesen und den Rest an einen neuen Reader übergeben
const stream = new ReadableStream({
start(controller) {
controller.enqueue('HEADER:v2');
controller.enqueue('Datensatz 1');
controller.enqueue('Datensatz 2');
controller.close();
}
});
const peekHeader = async (s) => {
const reader = s.getReader();
const { value } = await reader.read();
reader.releaseLock(); // Stream für andere Konsumenten freigeben
return value;
};
const header = await peekHeader(stream);
console.log(header);
console.log('gesperrt?', stream.locked);
// Neuer Reader liest ab der zweiten Stelle weiter
const reader2 = stream.getReader();
while (true) {
const { done, value } = await reader2.read();
if (done) break;
console.log(value);
}
Sperre bei fetch() zuverlässig mit try/finally freigeben
const readFirstBytes = async (url, limit = 1024) => {
const response = await fetch(url);
const reader = response.body.getReader();
const chunks = [];
let total = 0;
try {
while (total < limit) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
total += value.byteLength;
}
} finally {
// Auch bei Fehlern freigeben, damit der Body weiterverwendbar bleibt
reader.releaseLock();
}
return { response, total };
};
const { response, total } = await readFirstBytes('https://example.com/');
console.log('gelesen:', total, 'Bytes; Body gesperrt?', response.body.locked);
Offene read()-Anfrage wird beim Freigeben abgelehnt
// Stream, der nie Daten liefert
const stream = new ReadableStream();
const reader = stream.getReader();
const pending = reader.read(); // wartet für immer
reader.releaseLock();
try {
await pending;
} catch (err) {
console.log(err.name);
}
await reader.closed.catch((err) => console.log('closed:', err.name));
console.log('locked:', stream.locked);
// Wichtig · Fallstricke
- Geändertes Verhalten: Ältere Browser (etwa Chrome vor 106, Firefox vor 102, Safari vor 16.4) werfen beim Aufruf einen
TypeError, wenn nochread()-Anfragen offen sind. Neuere Browser lehnen stattdessen die offenen Promises ab. Wenn der Code in beiden Fällen funktionieren soll, erst alleread()-Promises abwarten und danach freigeben. releaseLock()schließt den Stream nicht. Wer nicht mehr lesen will, ruftreader.cancel()auf. Sonst bleibt z. B. eine Netzwerkverbindung unnötig offen.- Nach dem Freigeben den alten Reader nicht weiterverwenden:
read()undcancel()werden dann mit einemTypeErrorabgelehnt. for await (const chunk of stream)gibt die Sperre am Ende oder beibreakautomatisch frei. Dabei wird der Stream aber standardmäßig abgebrochen, außer man nutztstream.values({ preventCancel: true }).- Die Freigabe gehört in einen
finally-Block. Sonst bleibt ein Stream nach einem Fehler dauerhaft gesperrt, und z. B.response.text()scheitert mit „body stream is locked“.