Gli stream in Node.js possono essere complicati, ma vale la pena dedicare del tempo a comprenderli.

Punti chiave

  • Gli stream in Node.js sono uno strumento fondamentale per l'elaborazione e il trasferimento dei dati, rendendoli ideali per applicazioni in tempo reale e basate sugli eventi.
  • Per creare un flusso scrivibile in Node.js, puoi utilizzare la funzione createWriteStream() del modulo fs, che scrive i dati in una posizione specifica.
  • Leggibile, scrivibile, duplex e trasformabile sono i quattro tipi di flussi in Node.js, ciascuno con il proprio caso d'uso e funzionalità.

Uno stream è uno strumento di programmazione fondamentale che si occupa del flusso di dati. Fondamentalmente, un flusso rappresenta tipicamente il trasferimento sequenziale di byte da un punto a un altro. La documentazione ufficiale di Node.js definisce uno stream come un'interfaccia astratta che puoi utilizzare per lavorare con i dati.

Il trasferimento di dati su un computer o attraverso una rete è un utilizzo ideale di un flusso.

instagram viewer

Stream in Node.js

Gli stream hanno svolto un ruolo essenziale nel successo di Node.js. Sono ideali per l'elaborazione dei dati in tempo reale e le applicazioni guidate dagli eventi, due caratteristiche importanti dell'ambiente runtime Node.js.

Per creare un nuovo stream in Node.js, dovrai utilizzare l'API stream, che funziona esclusivamente con Strings e Dati del buffer di Node.js. Node.js ha quattro tipi di flussi: scrivibile, leggibile, duplex e trasformazione.

Come creare e utilizzare un flusso scrivibile

Un flusso scrivibile ti consente di scrivere o inviare dati a una posizione specifica. Il modulo fs (file system) ha una classe WriteStream, che puoi utilizzare per creare un nuovo flusso con il file fs.createWriteStream() funzione. Questa funzione accetta il percorso del file in cui desideri scrivere i dati, nonché una serie opzionale di opzioni.

const {createWriteStream} = require("fs");

(() => {
const file = "myFile.txt";
const myWriteStream = createWriteStream(file);
let x = 0;
const writeNumber = 10000;

const writeData = () => {
while (x < writeNumber) {
const chunk = Buffer.from(`${x}, `, "utf-8");
if (x writeNumber - 1) return myWriteStream.end(chunk);
if (!myWriteStream.write(chunk)) break;
x++
}
};

writeData();
})();

Questo codice importa il file createWriteStream() funzione, che la funzione freccia anonima quindi utilizza per creare un flusso che scrive i dati in myFile.txt. La funzione anonima contiene una funzione interna chiamata scriveredati() che scrive dati.

IL createWriteStream() la funzione funziona con un buffer per scrivere una raccolta di numeri (0–9.999) nel file di destinazione. Tuttavia, quando esegui lo script sopra, crea un file nella stessa directory che contiene i seguenti dati:

L'attuale raccolta di numeri termina con 2.915, ma avrebbe dovuto includere numeri fino a 9.999. Questa discrepanza si verifica perché ogni WriteStream utilizza un buffer che memorizza una quantità fissa di dati alla volta. Per sapere qual è questo valore predefinito, dovrai consultare il segno dell'acqua alta opzione.

console.log("The highWaterMark value is: " +
myWriteStream.writableHighWaterMark + " bytes.");

L'aggiunta della riga di codice sopra alla funzione anonima produrrà il seguente output nel terminale:

L'output del terminale mostra che il file default segno dell'acqua alta Il valore (che è personalizzabile) è 16.384 byte. Ciò significa che in questo buffer è possibile memorizzare solo meno di 16.384 byte di dati alla volta. Pertanto, fino al numero 2.915 (più tutte le virgole e gli spazi) rappresenta la quantità massima di dati che il buffer può archiviare contemporaneamente.

La soluzione all'errore del buffer consiste nell'utilizzare un evento di flusso. Un flusso incontra vari eventi in fasi distinte del processo di trasferimento dei dati. IL drenare event è l'opzione adatta per questa situazione.

Nel scriveredati() funzione sopra, la chiamata a La scrittura di WriteStream() la funzione restituisce true se il blocco di dati (o buffer interno) è inferiore a segno dell'acqua alta valore. Ciò indica che l'applicazione può inviare più dati al flusso. Tuttavia, non appena il scrivere() la funzione restituisce false il ciclo si interrompe perché è necessario svuotare il buffer.

myWriteStream.on('drain', () => {
console.log("a drain has occurred...");
writeData();
});

Inserendo il drenare il codice evento sopra nella funzione anonima svuoterà il file Buffer di WriteStream quando è al completo. Poi, ricorda il scriveredati() metodo, in modo che possa continuare a scrivere i dati. L'esecuzione dell'applicazione aggiornata produrrà il seguente output:

Dovresti notare che l'applicazione ha dovuto svuotare il file Buffer WriteStream tre volte durante la sua esecuzione. Anche il file di testo ha subito alcune modifiche:

Come creare e utilizzare un flusso leggibile

Per leggere i dati, inizia creando un flusso leggibile utilizzando il file fs.createReadStream() funzione.

const {createReadStream} = require("fs");

(() => {
const file = "myFile.txt";
const myReadStream = createReadStream(file);

myReadStream.on("open", () => {
console.log(`The read stream has successfully opened ${file}.`);
});

myReadStream.on("data", chunk => {
console.log("The file contains the following data: " + chunk.toString());
});

myReadStream.on("close", () => {
console.log("The file has been successfully closed.");
});
})();

Lo script sopra utilizza il file createReadStream() metodo per accedere al file creato dal codice precedente: myFile.txt. IL createReadStream() La funzione accetta un percorso di file (che può essere sotto forma di stringa, buffer o URL) e diverse opzioni facoltative come argomenti.

Nella funzione anonima sono presenti diversi eventi di flusso importanti. Tuttavia, non c'è traccia di drenare evento. Questo perché un flusso leggibile memorizza nel buffer i dati solo quando chiami il file stream.push (pezzo) funzione o utilizzare il leggibile evento.

IL aprire l'evento si attiva quando fs apre il file da cui vuoi leggere. Quando alleghi il file dati evento a un flusso implicitamente continuo, fa sì che il flusso passi alla modalità flusso. Ciò consente ai dati di passare non appena diventano disponibili. L'esecuzione dell'applicazione sopra produce il seguente output:

Come creare e utilizzare un flusso duplex

Un flusso duplex implementa sia l'interfaccia del flusso scrivibile che quella leggibile, quindi è possibile leggere e scrivere su tale flusso. Un esempio è un socket TCP che si basa sul modulo net per la sua creazione.

Un modo semplice per dimostrare le proprietà di un flusso duplex consiste nel creare un server e un client TCP che trasferiscano i dati.

Il file server.js

const net = require('net');
const port = 5000;
const host = '127.0.0.1';

const server = net.createServer();

server.on('connection', (socket)=> {
console.log('Connection established from client.');

socket.on('data', (data) => {
console.log(data.toString());
});

socket.write("Hi client, I am server " + server.address().address);

socket.on('close', ()=> {
console.log('the socket is closed')
});
});

server.listen(port, host, () => {
console.log('TCP server is running on port: ' + port);
});

Il file client.js

const net = require('net');
const client = new net.Socket();
const port = 5000;
const host = '127.0.0.1';

client.connect(port, host, ()=> {
console.log("connected to server!");
client.write("Hi, I'm client " + client.address().address);
});

client.on('data', (data) => {
console.log(data.toString());
client.write("Goodbye");
client.end();
});

client.on('end', () => {
console.log('disconnected from server.');
});

Noterai che sia gli script del server che quelli del client utilizzano un flusso leggibile e scrivibile per comunicare (trasferire e ricevere dati). Naturalmente, l'applicazione server viene eseguita per prima e inizia ad ascoltare le connessioni. Non appena si avvia il client, si connette al server utilizzando il numero della porta TCP.

Dopo aver stabilito una connessione, il client avvia il trasferimento dei dati scrivendo al server utilizzando il suo WriteStream. Il server registra i dati che riceve sul terminale, quindi scrive i dati utilizzando il suo WriteStream. Infine, il client registra i dati ricevuti, scrive dati aggiuntivi e quindi si disconnette dal server. Il server rimane aperto per la connessione di altri client.

Come creare e utilizzare un flusso di trasformazione

I flussi di trasformazione sono flussi duplex in cui l'output è correlato, ma diverso dall'input. Node.js ha due tipi di flussi Transform: flussi zlib e crittografici. Un flusso zlib può comprimere un file di testo e quindi decomprimerlo dopo il trasferimento del file.

L'applicazione compressFile.js

const zlib = require('zlib');
const { createReadStream, createWriteStream } = require('fs');

(() => {
const source = createReadStream('myFile.txt');
const destination = createWriteStream('myFile.txt.gz');

source.pipe(zlib.createGzip()).pipe(destination);
})();

Questo semplice script prende il file di testo originale, lo comprime e lo memorizza nella directory corrente. Questo è un processo semplice grazie allo stream leggibile tubo() metodo. Le pipeline di flusso rimuovono l'uso di buffer e convogliano i dati direttamente da un flusso a un altro.

Tuttavia, prima che i dati raggiungano il flusso scrivibile nello script, è necessaria una piccola deviazione tramite il metodo createGzip() di zlib. Questo metodo comprime il file e restituisce un nuovo oggetto Gzip che il flusso di scrittura riceve quindi.

L'applicazione decompressFile.js

const zlib = require('zlib'); 
const { createReadStream, createWriteStream } = require('fs');
 
(() => {
const source = createReadStream('myFile.txt.gz');
const destination = createWriteStream('myFile2.txt');

source.pipe(zlib.createUnzip()).pipe(destination);
})();

Questo script sopra prende il file compresso e lo decomprime. Se apri il nuovo mioFile2.txt file, vedrai che contiene gli stessi dati del file originale:

Perché gli stream sono importanti?

I flussi migliorano l'efficienza del trasferimento dei dati. I flussi leggibili e scrivibili costituiscono la base che consente la comunicazione tra client e server, nonché la compressione e il trasferimento di file di grandi dimensioni.

Gli stream migliorano anche le prestazioni dei linguaggi di programmazione. Senza flussi, il processo di trasferimento dei dati diventa più complesso, richiedendo un maggiore input manuale da parte degli sviluppatori e comportando più errori e problemi di prestazioni.