Node.js
Usando i flussi
Ricerca…
Parametri
Parametro | Definizione |
---|---|
Stream leggibile | tipo di flusso da cui è possibile leggere i dati |
Flusso scrivibile | tipo di flusso in cui i dati possono essere scritti |
Duplex Stream | tipo di stream che è sia leggibile che scrivibile |
Trasforma stream | tipo di flusso duplex in grado di trasformare i dati mentre vengono letti e quindi scritti |
Leggi i dati da TextFile con flussi
L'I / O nel nodo è asincrono, quindi l'interazione con il disco e la rete implica il passaggio di callback alle funzioni. Potresti essere tentato di scrivere il codice che serve un file dal disco in questo modo:
var http = require('http');
var fs = require('fs');
var server = http.createServer(function (req, res) {
fs.readFile(__dirname + '/data.txt', function (err, data) {
res.end(data);
});
});
server.listen(8000);
Questo codice funziona ma è ingombrante e memorizza l'intero file data.txt in memoria per ogni richiesta prima di scrivere il risultato ai client. Se data.txt è molto grande, il tuo programma potrebbe iniziare a consumare molta memoria poiché serve molti utenti contemporaneamente, in particolare per gli utenti con connessioni lente.
Anche l'esperienza utente è scadente perché gli utenti dovranno attendere che l'intero file venga memorizzato in memoria sul server prima che possano iniziare a ricevere qualsiasi contenuto.
Fortunatamente entrambi gli argomenti (req, res) sono stream, il che significa che possiamo scrivere questo in un modo molto migliore usando fs.createReadStream () invece di fs.readFile ():
var http = require('http');
var fs = require('fs');
var server = http.createServer(function (req, res) {
var stream = fs.createReadStream(__dirname + '/data.txt');
stream.pipe(res);
});
server.listen(8000);
Qui .pipe () si occupa di ascoltare gli eventi "data" e "end" da fs.createReadStream (). Questo codice non è solo più pulito, ma ora il file data.txt verrà scritto ai client un chunk alla volta immediatamente quando vengono ricevuti dal disco.
Flussi di tubazioni
Gli stream leggibili possono essere "convogliati" o collegati a flussi scrivibili. Ciò rende il flusso di dati dal flusso di origine al flusso di destinazione senza molti sforzi.
var fs = require('fs')
var readable = fs.createReadStream('file1.txt')
var writable = fs.createWriteStream('file2.txt')
readable.pipe(writable) // returns writable
Quando i flussi scrivibili sono anche flussi leggibili, vale a dire quando sono flussi duplex , è possibile continuare a collegarli ad altri flussi scrivibili.
var zlib = require('zlib')
fs.createReadStream('style.css')
.pipe(zlib.createGzip()) // The returned object, zlib.Gzip, is a duplex stream.
.pipe(fs.createWriteStream('style.css.gz')
Anche i flussi leggibili possono essere inviati in più flussi.
var readable = fs.createReadStream('source.css')
readable.pipe(zlib.createGzip()).pipe(fs.createWriteStream('output.css.gz'))
readable.pipe(fs.createWriteStream('output.css')
Si noti che è necessario reindirizzare i flussi di output in modo sincrono (allo stesso tempo) prima che i dati "scorrano". In caso contrario, i dati incompleti potrebbero essere trasmessi in streaming.
Si noti inoltre che gli oggetti stream possono emettere eventi di error
; assicurati di gestire responsabilmente questi eventi su ogni stream, se necessario:
var readable = fs.createReadStream('file3.txt')
var writable = fs.createWriteStream('file4.txt')
readable.pipe(writable)
readable.on('error', console.error)
writable.on('error', console.error)
Crea il tuo stream leggibile / scrivibile
Vedremo oggetti stream restituiti da moduli come fs etc, ma se vogliamo creare il nostro oggetto streamable.
Per creare un oggetto Stream è necessario utilizzare il modulo stream fornito da NodeJs
var fs = require("fs");
var stream = require("stream").Writable;
/*
* Implementing the write function in writable stream class.
* This is the function which will be used when other stream is piped into this
* writable stream.
*/
stream.prototype._write = function(chunk, data){
console.log(data);
}
var customStream = new stream();
fs.createReadStream("am1.js").pipe(customStream);
Questo ci darà il nostro flusso scrivibile personalizzato. possiamo implementare qualsiasi cosa all'interno della funzione _write . Il metodo sopra funziona nella versione 4.xx di NodeJs ma in NodeJs 6.x ES6 ha introdotto le classi, pertanto la sintassi è stata modificata. Di seguito è riportato il codice per la versione 6.x di NodeJs
const Writable = require('stream').Writable;
class MyWritable extends Writable {
constructor(options) {
super(options);
}
_write(chunk, encoding, callback) {
console.log(chunk);
}
}
Perché i flussi?
Esaminiamo i seguenti due esempi per leggere il contenuto di un file:
Il primo, che utilizza un metodo asincrono per leggere un file e fornisce una funzione di callback che viene chiamata una volta che il file è stato completamente letto nella memoria:
fs.readFile(`${__dirname}/utils.js`, (err, data) => {
if (err) {
handleError(err);
} else {
console.log(data.toString());
}
})
E il secondo, che utilizza i streams
per leggere il contenuto del file, pezzo per pezzo:
var fileStream = fs.createReadStream(`${__dirname}/file`);
var fileContent = '';
fileStream.on('data', data => {
fileContent += data.toString();
})
fileStream.on('end', () => {
console.log(fileContent);
})
fileStream.on('error', err => {
handleError(err)
})
Vale la pena ricordare che entrambi gli esempi fanno esattamente la stessa cosa . Qual è la differenza allora?
- Il primo è più corto e sembra più elegante
- Il secondo consente di eseguire qualche elaborazione sul file mentre viene letto (!)
Quando i file con cui si gestiscono sono piccoli, non c'è alcun effetto reale quando si usano gli streams
, ma cosa succede quando il file è grande? (così grande che ci vogliono 10 secondi per leggerlo in memoria)
Senza streams
ti aspetteresti, senza fare assolutamente nulla (a meno che il tuo processo non faccia altro), fino a quando non passeranno i 10 secondi e il file sarà completamente letto , e solo allora potrai iniziare l'elaborazione del file.
Con i streams
, si ottiene il contenuto del file pezzo per pezzo, proprio quando sono disponibili , e ciò consente di elaborare il file mentre viene letto.
L'esempio precedente non illustra come gli streams
possono essere utilizzati per lavori che non possono essere eseguiti quando si fa il callback, quindi guardiamo un altro esempio:
Vorrei scaricare un file gzip
, decomprimerlo e salvarne il contenuto sul disco. Dato l' url
del file, questo è ciò che è necessario fare:
- Scarica il file
- Decomprimere il file
- Salvalo su disco
Ecco un [file piccolo] [1], che è memorizzato nella mia memoria S3
. Il seguente codice fa quanto sopra nel modo callback.
var startTime = Date.now()
s3.getObject({Bucket: 'some-bucket', Key: 'tweets.gz'}, (err, data) => {
// here, the whole file was downloaded
zlib.gunzip(data.Body, (err, data) => {
// here, the whole file was unzipped
fs.writeFile(`${__dirname}/tweets.json`, data, err => {
if (err) console.error(err)
// here, the whole file was written to disk
var endTime = Date.now()
console.log(`${endTime - startTime} milliseconds`) // 1339 milliseconds
})
})
})
// 1339 milliseconds
Ecco come appare usando i streams
:
s3.getObject({Bucket: 'some-bucket', Key: 'tweets.gz'}).createReadStream()
.pipe(zlib.createGunzip())
.pipe(fs.createWriteStream(`${__dirname}/tweets.json`));
// 1204 milliseconds
Sì, non è più veloce quando si ha a che fare con file di piccole dimensioni: il file testato pesa 80KB
. Provando questo su un file più grande, 71MB
gzipped ( 382MB
decompresso), mostra che la versione degli streams
è molto più veloce
- Sono stati necessari 20925 millisecondi per scaricare
71MB
, decomprimerlo e quindi scrivere382MB
su disco, utilizzando la modalità callback . - In confronto, ci sono voluti 13434 millisecondi per fare lo stesso quando si utilizza la versione di
streams
(35% più veloce, per un file non così grande)