Signatur
Beschreibung
Die Methode textStream() der Schnittstelle Blob (und damit auch von File) liefert einen ReadableStream. Er gibt den Inhalt des Blobs Stück für Stück als bereits dekodierte Strings aus. Die Bytes werden dabei als UTF-8 gelesen. Damit verbindet sie die Vorteile von Blob.stream() (Verarbeitung in Stücken, geringer Speicherbedarf) und Blob.text() (fertiger Text statt Bytes).
Sinnvoll ist die Methode vor allem bei großen Textdateien wie Logdateien, CSV-Exporten oder NDJSON, die man nicht komplett in einen einzigen String laden will. Stattdessen verarbeitet man den Text beim Lesen, etwa Zeile für Zeile, zählt dabei mit oder durchsucht ihn nach Mustern.
Die Grenzen der Chunks richten sich nach der internen Pufferung, nicht nach Zeilen oder Zeichen mit fachlicher Bedeutung. Eine Zeile kann also über mehrere Chunks verteilt sein. Wer zeilenweise arbeiten will, muss einen Rest-Puffer mitführen. Da die Methode noch nicht breit unterstützt wird, ist die gleichwertige, überall lauffähige Variante blob.stream().pipeThrough(new TextDecoderStream()) in der Praxis meist die bessere Wahl.
Rückgabewert
ReadableStream, dessen Chunks UTF-8-dekodierte Strings sind. Der Stream wird geschlossen, sobald der ganze Blob gelesen ist.Beispiele
Text-Chunks eines Blobs lesen (mit Ersatzlösung)
// Ersatzlösung, falls textStream() nicht vorhanden ist
const getTextStream = (blob) =>
typeof blob.textStream === 'function'
? blob.textStream()
: blob.stream().pipeThrough(new TextDecoderStream());
const blob = new Blob(['Grüße aus ', 'Köln! ✓'], { type: 'text/plain' });
const main = async () => {
let text = '';
for await (const chunk of getTextStream(blob)) {
text += chunk; // chunk ist bereits ein String
}
console.log(text);
};
main();
Große Datei aus <input type="file"> zeilenweise verarbeiten
const getTextStream = (blob) =>
typeof blob.textStream === 'function'
? blob.textStream()
: blob.stream().pipeThrough(new TextDecoderStream());
const countErrorLines = async (file) => {
let rest = '';
let errors = 0;
const reader = getTextStream(file).getReader();
while (true) {
const { value, done } = await reader.read();
if (done) break;
const lines = (rest + value).split('\n');
rest = lines.pop(); // unvollständige letzte Zeile aufheben
errors += lines.filter((l) => l.includes('ERROR')).length;
}
if (rest.includes('ERROR')) errors++;
return errors;
};
document.querySelector('#logfile').addEventListener('change', async (e) => {
const [file] = e.target.files;
if (!file) return;
console.log(`Fehlerzeilen: ${await countErrorLines(file)}`);
});
NDJSON-Blob als Objekte weiterreichen
const getTextStream = (blob) =>
typeof blob.textStream === 'function'
? blob.textStream()
: blob.stream().pipeThrough(new TextDecoderStream());
// TransformStream: Text-Chunks -> JSON-Objekte je Zeile
const ndjsonParser = () => {
let rest = '';
return new TransformStream({
transform(chunk, controller) {
const lines = (rest + chunk).split('\n');
rest = lines.pop();
for (const line of lines) if (line.trim()) controller.enqueue(JSON.parse(line));
},
flush(controller) {
if (rest.trim()) controller.enqueue(JSON.parse(rest));
},
});
};
const blob = new Blob(['{"id":1}\n{"id"', ':2}\n{"id":3}']);
const main = async () => {
for await (const obj of getTextStream(blob).pipeThrough(ndjsonParser())) {
console.log(obj.id);
}
};
main();
// Wichtig · Fallstricke
- Kompatibilität:
textStream()ist sehr neu bzw. experimentell und in den gängigen Browsern und in Node.js bislang nicht verlässlich vorhanden. Prüfe deshalb immer mittypeof blob.textStream === 'function', ob die Methode existiert, und greife sonst aufblob.stream().pipeThrough(new TextDecoderStream())zurück. Diese Variante funktioniert in allen aktuellen Browsern und ab Node.js 18. - Immer UTF-8: Eine andere Zeichenkodierung lässt sich nicht angeben. Bei Dateien in Latin-1 oder Windows-1252 werden Umlaute falsch dargestellt. In dem Fall hilft
blob.stream().pipeThrough(new TextDecoderStream('windows-1252')). - Chunk-Grenzen: Chunks enden nicht an Zeilengrenzen. Für zeilenweises Verarbeiten braucht man einen Rest-Puffer (siehe Beispiele). Mehrbyte-Zeichen werden dagegen korrekt über Chunk-Grenzen hinweg dekodiert.
- Sperre: Hat man mit
getReader()einen Reader geholt, ist der Stream gesperrt. Man muss ihn mitreader.releaseLock()freigeben oder mitreader.cancel()abbrechen. Mitfor await...ofüber einenReadableStreamlässt sich direkt iterieren, das kann Safari aber erst in neueren Versionen. - Für kleine Blobs ist
await blob.text()einfacher und meist schneller.