Stream: dati a pezzi
Prima di iniziare: le parole della lezione
- Chunk, stream e buffering
- Un chunk è una porzione di dati. Uno stream permette di leggerne o scriverne una sequenza; il buffering conserva temporaneamente porzioni in memoria.
- Readable, Writable, Duplex e Transform
- Readable produce dati; Writable li riceve; Duplex può fare entrambe le cose; Transform è uno stream duplex che trasforma ciò che riceve, come una compressione.
- Backpressure e pipeline
- La backpressure rallenta il produttore quando il consumatore non tiene il passo. Una pipeline collega più stream e ne coordina completamento ed errori.
Uno stream gestisce dati in modo incrementale, senza necessariamente caricare l'intero contenuto in memoria. È utile per file grandi, download e trasferimenti HTTP.
// copia.cjs
const fs = require('node:fs');
const { pipeline } = require('node:stream/promises');
(async () => {
try {
await pipeline(
fs.createReadStream('origine.txt'),
fs.createWriteStream('copia.txt')
);
console.log('Copia completata');
} catch (errore) { console.error(errore.message); }
})();
Esempio aggiuntivo: con gli stream si può inviare al client un file di grandi dimensioni mentre lo si legge. pipeline() propaga in modo più affidabile errori e chiusura delle risorse rispetto a un semplice pipe() usato senza gestione degli errori.
Chi fa cosa, e perché
readFile rende disponibile il file completo; createReadStream lo fornisce a porzioni. Nel corso gli eventi data mostrano i chunk: la loro dimensione non coincide necessariamente con una riga o con un messaggio applicativo. Un file può quindi essere letto senza tenerlo tutto in memoria contemporaneamente.
highWaterMark controlla una soglia del buffer interno, non un limite rigido alla memoria totale. pipe collega una sorgente a una destinazione e gestisce il flusso; pipeline aiuta a coordinare anche errori e chiusura. Le richieste e risposte HTTP di Node sono a loro volta stream.
Un altro esempio guidato
// dimensione.mjs — crea prima origine.txt; node dimensione.mjs
import { createReadStream } from 'node:fs';
let byte = 0;
try {
for await (const chunk of createReadStream('origine.txt')) {
byte += chunk.length;
}
console.log('Byte letti:', byte);
} catch (errore) {
console.error('Lettura fallita:', errore.message);
}
Come leggerlo e risultato atteso. for await...of consuma le porzioni disponibili senza dover scrivere a mano i listener data ed end. Senza encoding i chunk sono Buffer e length conta byte: lettere accentate UTF-8 possono occupare più di un byte.
Prova tu. Crea un file con caratteri accentati e confronta il numero di byte con il numero di caratteri del testo. Non sommare chunk.toString().length come se fossero sempre la stessa misura.