Node.js
Streams gebruiken
Zoeken…
parameters
Parameter | Definitie |
---|---|
Leesbare stroom | type stream waar gegevens van kunnen worden gelezen |
Beschrijfbare stream | type stream waarnaar gegevens kunnen worden geschreven |
Duplex stream | type stream dat zowel leesbaar als beschrijfbaar is |
Transformeer stream | type duplexstroom die gegevens kan transformeren tijdens het lezen en vervolgens schrijven |
Gegevens lezen uit TextFile met streams
I / O in node is asynchroon, dus interactie met de schijf en het netwerk houdt in dat callbacks worden doorgegeven aan functies. U kunt in de verleiding komen om code te schrijven die een bestand als volgt van schijf dient:
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);
Deze code werkt maar is omvangrijk en buffert het hele data.txt-bestand voor elke aanvraag in het geheugen voordat het resultaat naar clients wordt weggeschreven. Als data.txt erg groot is, kan je programma veel geheugen gaan gebruiken omdat het tegelijkertijd veel gebruikers bedient, met name voor gebruikers met trage verbindingen.
De gebruikerservaring is ook slecht omdat gebruikers moeten wachten tot het hele bestand in het geheugen op uw server is opgeslagen voordat ze inhoud kunnen ontvangen.
Gelukkig zijn beide (req, res) argumenten streams, wat betekent dat we dit op een veel betere manier kunnen schrijven met fs.createReadStream () in plaats van 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);
Hier zorgt .pipe () voor het luisteren naar 'data' en 'end' evenementen van de fs.createReadStream (). Deze code is niet alleen schoner, maar nu wordt het bestand data.txt direct één voor één naar clients geschreven zodra ze van de schijf worden ontvangen.
Leidingstromen
Leesbare streams kunnen worden "piped" of verbonden met beschrijfbare streams. Hierdoor stroomt de gegevens zonder veel moeite van de bronstroom naar de doelstroom.
var fs = require('fs')
var readable = fs.createReadStream('file1.txt')
var writable = fs.createWriteStream('file2.txt')
readable.pipe(writable) // returns writable
Wanneer beschrijfbare streams ook leesbare streams zijn, dat wil zeggen wanneer het duplexstreams zijn, kunt u doorgaan met piping naar andere beschrijfbare streams.
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')
Leesbare streams kunnen ook in meerdere streams worden ondergebracht.
var readable = fs.createReadStream('source.css')
readable.pipe(zlib.createGzip()).pipe(fs.createWriteStream('output.css.gz'))
readable.pipe(fs.createWriteStream('output.css')
Merk op dat u synchroon (tegelijkertijd) naar de uitgangsstromen moet gaan voordat er gegevens 'stromen'. Als u dit niet doet, kunnen onvolledige gegevens worden gestreamd.
Merk ook op dat de stroom voorwerpen kan uitzenden error
gebeurtenissen; zorg ervoor dat je deze evenementen op elke stream op verantwoorde wijze afhandelt, indien nodig:
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)
Je eigen leesbare / schrijfbare stream maken
We zullen zien dat streamobjecten worden geretourneerd door modules zoals fs enz. Maar wat als we ons eigen streamable object willen maken.
Om een Stream-object te maken, moeten we de streammodule van NodeJs gebruiken
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);
Dit geeft ons onze eigen aangepaste beschrijfbare stream. we kunnen alles binnen de _write- functie implementeren. Bovenstaande methode werkt in NodeJs 4.xx-versie maar in NodeJs 6.x ES6 geïntroduceerde klassen, daarom is de syntaxis gewijzigd. Hieronder staat de code voor 6.x-versie van NodeJs
const Writable = require('stream').Writable;
class MyWritable extends Writable {
constructor(options) {
super(options);
}
_write(chunk, encoding, callback) {
console.log(chunk);
}
}
Waarom stromen?
Laten we de volgende twee voorbeelden bekijken voor het lezen van de inhoud van een bestand:
De eerste, die een asynchrone methode gebruikt om een bestand te lezen en een callback-functie biedt die wordt aangeroepen zodra het bestand volledig in het geheugen is gelezen:
fs.readFile(`${__dirname}/utils.js`, (err, data) => {
if (err) {
handleError(err);
} else {
console.log(data.toString());
}
})
En de tweede, die streams
gebruikt om de inhoud van het bestand stuk voor stuk te lezen:
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)
})
Het is vermeldenswaard dat beide voorbeelden exact hetzelfde doen . Wat is het verschil dan?
- De eerste is korter en ziet er eleganter uit
- Met de tweede kunt u wat verwerking van het bestand uitvoeren terwijl het wordt gelezen (!)
Wanneer de bestanden waarmee u omgaat klein zijn, is er geen echt effect bij het gebruik van streams
, maar wat gebeurt er als het bestand groot is? (zo groot dat het 10 seconden duurt om het in het geheugen te lezen)
Zonder streams
wacht je en doe je helemaal niets (tenzij je proces andere dingen doet), totdat de 10 seconden voorbij zijn en het bestand volledig is gelezen , en alleen dan kun je het bestand verwerken.
Met streams
krijg je de inhoud van het bestand stuk voor stuk, precies wanneer het beschikbaar is - en daarmee kun je het bestand verwerken terwijl het wordt gelezen.
Het bovenstaande voorbeeld illustreert niet hoe streams
kunnen worden gebruikt voor werk dat niet kan worden gedaan wanneer de callback-modus wordt gebruikt, dus laten we een ander voorbeeld bekijken:
Ik wil een gzip
bestand downloaden, uitpakken en de inhoud ervan op de schijf opslaan. Gezien de url
het bestand moet dit worden gedaan:
- Download het bestand
- Pak het bestand uit
- Bewaar het op schijf
Hier is een [klein bestand] [1], dat is opgeslagen in mijn S3
opslag. De volgende code doet het bovenstaande op de manier van terugbellen.
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
Zo ziet het eruit met streams
:
s3.getObject({Bucket: 'some-bucket', Key: 'tweets.gz'}).createReadStream()
.pipe(zlib.createGunzip())
.pipe(fs.createWriteStream(`${__dirname}/tweets.json`));
// 1204 milliseconds
Ja, het is niet sneller bij het omgaan met kleine bestanden - het geteste bestand weegt 80KB
. Dit testen op een groter bestand, 71MB
gzipped ( 382MB
), laat zien dat de streams
versie veel sneller is
- Het kostte 20925 milliseconden om
71MB
MB te downloaden, uit te pakken en vervolgens382MB
naar schijf te schrijven - op de callback-manier . - Ter vergelijking: het kostte 13434 milliseconden om hetzelfde te doen bij het gebruik van de
streams
versie (35% sneller, voor een niet zo groot bestand)