Node.js-streams: FileStream, Pipes en Events

โšก Slimme samenvatting

Node.js-streams lezen en schrijven gegevens in kleine stukjes in plaats van hele bestanden in het geheugen te laden. Deze pagina legt uit hoe je een bestandsstream aanmaakt, pijplijnen koppelt, backpressure afhandelt en het gebeurtenismodel gebruikt dat zorgt voor efficiรซnte gegevensoverdracht op grote schaal.

  • ???? Streamtypen: Node.js categoriseert elke stream als leesbaar, schrijfbaar, duplex of transformeerbaar, en elke Node.js API retourneert een van deze vier typen.
  • ๐Ÿ“‚ Bestandsstroom: fs.createReadStream() en fs.createWriteStream() lezen en schrijven bestanden in stukken zonder het hele bestand in het geheugen te laden.
  • ๐Ÿ”— Piping: De pipe()-methode verbindt een leesbare stream rechtstreeks met een schrijfbare stream en verplaatst gegevens automatisch tussen beide.
  • โ€‹ Tegendruk: write() retourneert false wanneer de buffer vol is, en de drain-gebeurtenis geeft aan wanneer het veilig is om het schrijven te hervatten.
  • ๐Ÿ“ก Stream evenementen: Data, end, error en finish markeren de belangrijkste momenten in de levenscyclus van een stream en vormen de basis voor de meeste code die met streams werkt.
  • โšก Aangepaste evenementen: De events-module en EventEmitter stellen elke Node.js-applicatie in staat om eigen, benoemde gebeurtenissen te definiรซren, te verzenden en te ontvangen.
  • ๏ธ Luisteraarbesturing: De functies once(), listenerCount() en de newListener-gebeurtenis bepalen hoe vaak een handler wordt uitgevoerd en hoe luisteraars worden verwerkt. tracken.

Soorten streams in Node.js

Elke stream die Node.js beschikbaar stelt, valt in een van de vier basistypen. Weten welk type een bepaalde API retourneert, verklaart hoe die stream zich gedraagt โ€‹โ€‹voordat er code wordt geschreven. De stream-module die met Node.js wordt meegeleverd, definieert deze categorieรซn, en de API's voor bestanden, netwerken en compressie die in dit artikel worden gebruikt, zijn daarop gebouwd.

Stroomtype Gegevensrichting Voorbeeld-API Veelvoorkomend gebruiksscenario
Readable Alleen bron (data stroomt naar buiten) fs.createReadStream() Een bestand lezen en een HTTP-verzoekbody ontvangen.
Beschrijfbaar Alleen bestemming (gegevensstromen komen binnen) fs.createWriteStream() Een bestand schrijven, een HTTP-antwoord verzenden
Duplex Beide richtingen, onafhankelijk van elkaar. net.Socket (TCP-socket) Tweewegs netwerkcommunicatie
Transformeren In beide richtingen worden de gegevens tijdens de overdracht gewijzigd. zlib.createGzip() Gegevens comprimeren of coderen tijdens het verwerkingsproces.

Een leesbare stream en een schrijfbare stream zijn de twee bouwstenen die in dit artikel worden gebruikt; beide vormen ook de basis voor een HTTP-webserver In Node.js combineert een duplexstream beide rollen in รฉรฉn object, terwijl een transformstream een โ€‹โ€‹duplexstream is waarvan de uitvoer de invoer wijzigt, zoals bijvoorbeeld gzip-compressie die halverwege de stream wordt toegepast.

Filestream in Node.js

Nu de vier streamcategorieรซn zijn gedefinieerd, wordt in de rest van dit artikel een bestandsoverdracht opgebouwd met behulp van de API's voor leesbare en beschrijfbare streams.

Node maakt uitgebreid gebruik van streams als mechanisme voor gegevensoverdracht.

Wanneer u bijvoorbeeld iets naar de console uitvoert met behulp van de console.log-functie, gebruikt u feitelijk een stream om de gegevens naar de console te verzenden.

Node.js heeft ook de mogelijkheid om gegevens uit bestanden te streamen, zodat deze op de juiste manier kunnen worden gelezen en geschreven. We zullen nu kijken naar een voorbeeld van hoe we streams kunnen gebruiken om vanuit bestanden te lezen en te schrijven. Voor dit voorbeeld moeten we de onderstaande stappen volgen

Stap 1) Maak een bestand met de naam data.txt dat de onderstaande gegevens bevat. Laten we aannemen dat dit bestand is opgeslagen op de D-schijf van onze lokale machine.

Zelfstudie over Node.js

Introductie

Evenementen

Generators

Data connectiviteit

Jasmijn gebruiken

Stap 2) Schrijf de relevante code die gebruik maakt van streams om gegevens uit het bestand te lezen.

Filestream in Node.js

var fs = require("fs");
var stream;
stream = fs.createReadStream("D://data.txt");

stream.on("data", function(data) {
    var chunk = data.toString();
    console.log(chunk);
}); 

Code Uitleg:-

  1. We moeten eerst de 'fs'-modules toevoegen die alle functionaliteit bevatten die nodig is om streams te maken.
  2. Vervolgens maken we een leesbare stream met behulp van de methode โ€“ createReadStream. Als invoer geven we de locatie van ons data.txt-bestand.
  3. De steam.on-functie is een gebeurtenishandler en daarin specificeren we de eerste parameter als 'data'. Dit betekent dat wanneer er gegevens uit het bestand in de stroom komen, er een callback-functie wordt uitgevoerd. In ons geval definiรซren we een callback-functie die twee basisstappen uitvoert. De eerste is om de uit het bestand gelezen gegevens als een string te converteren. De tweede zou zijn om de geconverteerde string als uitvoer naar de console te sturen.
  4. We nemen elk stuk gegevens dat uit de datastroom wordt gelezen en converteren het naar een string.
  5. Ten slotte sturen we de uitvoer van elk string-geconverteerd deel naar de console.

Output:

Filestream in Node.js

  • Als de code correct wordt uitgevoerd, ziet u de bovenstaande uitvoer in de console. Deze uitvoer is dezelfde als die in het data.txt-bestand.

Schrijven naar een bestand

Op dezelfde manier waarop we een leesstroom maken, kunnen we ook een schrijfstroom maken om gegevens naar een bestand te schrijven. Laten we eerst een leeg bestand maken zonder inhoud, genaamd data.txt. Laten we aannemen dat dit bestand op de D-schijf van onze computer is geplaatst.

De onderstaande code laat zien hoe we gegevens naar het bestand kunnen schrijven.

Filestream in Node.js

var fs = require("fs");
var stream;
stream = fs.createWriteStream("D://data.txt");

stream.write("Tutorial on Node.js")
stream.write("Introduction")
stream.write("Events")
stream.write("Generators")
stream.write("Data Connectivity")
stream.write("Using Jasmine") 

Code Uitleg:-

  1. We maken een beschrijfbare stream met behulp van de methode createWriteStream. Als invoer geven we de locatie van ons data.txt-bestand.
  2. Vervolgens gebruikten we de stream.write een methode om de verschillende regels tekst naar ons tekstbestand te schrijven. De stream zorgt ervoor dat deze gegevens naar het data.txt-bestand worden geschreven.

Als u het bestand data.txt opent, ziet u nu de volgende gegevens in het bestand

Zelfstudie over Node.js

Introductie

Evenementen

Generators

Data connectiviteit

gebruik Jasmijn

Leidingen in Node.js

Het onafhankelijk lezen en schrijven van streams is handig, maar Node.js maakt het ook mogelijk om een โ€‹โ€‹leesbare stream rechtstreeks te verbinden met een schrijfbare stream, waardoor gegevens kunnen worden overgedragen zonder handmatige buffering.

Binnen Node-toepassingen kunnen streams aan elkaar worden gekoppeld met behulp van de pipe()-methode, waarvoor twee argumenten nodig zijn:

  • Een vereiste beschrijfbare stream die fungeert als bestemming voor de gegevens en
  • Een optioneel object dat wordt gebruikt om opties door te geven.

Een typisch voorbeeld van het gebruik van pipelines, als u gegevens van het ene bestand naar het andere wilt overbrengen.

Laten we dus een voorbeeld bekijken van hoe we gegevens van het ene bestand naar het andere kunnen overbrengen met behulp van pipelines.

Stap 1) Maak een bestand met de naam datainput.txt dat de onderstaande gegevens bevat. Laten we aannemen dat dit bestand is opgeslagen op de D-schijf van onze lokale machine.

Zelfstudie over Node.js

Introductie

Evenementen

Generators

Data connectiviteit

Jasmijn gebruiken

Stap 2) Maak een leeg leeg bestand met de naam dataOutput.txt en plaats dit op de D-schijf van uw lokale machine.

Stap 3) Schrijf de onderstaande code om de gegevensoverdracht van het datainput.txt-bestand naar het dataOutput.txt-bestand uit te voeren.

Leidingen in Node.js

var fs = require("fs");
var readStream = fs.createReadStream("D://datainput.txt");
var writeStream = fs.createWriteStream("D://dataOutput.txt");
readStream.pipe(writeStream);

Code Uitleg:-

  1. We maken eerst een โ€œreadstreamโ€ naar ons datainput.txt-bestand dat al onze gegevens bevat die naar het nieuwe bestand moeten worden overgebracht.
  2. We moeten dan een โ€œwritestreamโ€ maken naar ons dataOutput.txt-bestand, wat ons lege bestand is en de bestemming is voor de overdracht van gegevens uit het datainput.txt-bestand.
  3. Vervolgens gebruiken we het pipe-commando om de gegevens van de leesstroom naar de schrijfstroom over te brengen. Het pipe-commando neemt alle gegevens die in de leesstroom binnenkomen en duwt deze naar de schrijfstroom.

Als u nu het bestand dataOutput.txt opent, ziet u alle gegevens die in het bestand datainput.txt aanwezig waren.

Streamgebeurtenissen en tegendruk in Node.js

Door pipe() aan te roepen, zoals hierboven weergegeven, worden twee mechanismen verborgen die op grote schaal van belang zijn: de gebeurtenissen die elke stream genereert, en het tegendruksysteem dat voorkomt dat een snelle bron een langzame bestemming overweldigt.

  • data: wordt door een leesbare stream uitgezonden telkens wanneer een nieuw gegevensblok beschikbaar is.
  • einde: wordt uitgezonden zodra een leesbare stream zijn laatste deel heeft afgeleverd.
  • af hebben: wordt door een beschrijfbare stream verzonden zodra alle in de wachtrij geplaatste gegevens zijn verwerkt.
  • fout: Deze foutmelding wordt door beide streamtypen gegenereerd bij een fout; handel deze altijd af, anders crasht het proces.

Het onderstaande voorbeeld luistert naar alle vier de gebeurtenissen en beheert handmatig de tegendruk, hetzelfde gedrag dat pipe() automatisch biedt.

var fs = require("fs");
var readStream = fs.createReadStream("D://data.txt");
var writeStream = fs.createWriteStream("D://dataOutput.txt");

readStream.on("data", function(chunk) {
    var ok = writeStream.write(chunk);
    if (!ok) {
        readStream.pause();
    }
});

writeStream.on("drain", function() {
    readStream.resume();
});

readStream.on("end", function() {
    writeStream.end();
});

writeStream.on("finish", function() {
    console.log("Write completed.");
});

readStream.on("error", function(err) {
    console.log(err);
});

Backpressure treedt op wanneer een leesbare stream sneller data produceert dan een schrijfbare stream deze kan verwerken. De write()-methode retourneert false zodra de buffer vol is, dus de bovenstaande code pauzeert de bron totdat de bestemming een drain-event uitzendt, wat aangeeft dat het veilig is om verder te gaan. pipe() voert deze cyclus automatisch uit.

โš  Tip: In een productieomgeving is het aan te raden de `pipeline()`-functie uit de `stream`-module te gebruiken in plaats van een handmatige `pipe()`-keten, omdat deze fouten van elke stream doorstuurt en bestandshendels automatisch sluit.

Gebeurtenissen in Node.js

Streams zijn een voorbeeld van Node.js' bredere, op gebeurtenissen gebaseerde ontwerp. De rest van dit artikel gaat dieper in op hoe gebeurtenissen in het algemeen werken, waarbij gebruik wordt gemaakt van dezelfde EventEmitter-klasse waarop streams intern gebaseerd zijn.

Gebeurtenissen zijn een van de belangrijkste concepten in Node.js. Soms wordt Node.js ook wel een gebeurtenisgestuurd framework genoemd.

Kortom, een gebeurtenis is iets dat gebeurt. Als er bijvoorbeeld een verbinding met een database tot stand wordt gebracht, wordt de databaseverbindingsgebeurtenis geactiveerd. Gebeurtenisgestuurd programmeren is het creรซren van functies die worden geactiveerd wanneer specifieke gebeurtenissen worden geactiveerd.

Laten we eens kijken naar een eenvoudig voorbeeld van het definiรซren van een gebeurtenis in Node.js.

We gaan een gebeurtenis maken met de naam 'data_received'. Wanneer deze gebeurtenis wordt geactiveerd, wordt de tekst โ€œdata ontvangenโ€ naar de console verzonden.

Gebeurtenissen in Node.js

var events = require('events');
var eventEmitter = new events.EventEmitter();
eventEmitter.on('data_received', function() {
    console.log('data received succesfully.');
});

eventEmitter.emit('data_received'); 

Code Uitleg:-

  1. Gebruik de functie require om de module 'evenementen' op te nemen. Met deze module kunt u evenementen maken in Node.js.
  2. Maak een nieuwe gebeurtenissenzender. Dit wordt gebruikt om de gebeurtenis, die in ons geval โ€œdata_receivedโ€ is, te binden aan een callback-functie die is gedefinieerd in stap 3.
  3. We definiรซren een gebeurtenisgestuurde functie die zegt dat als de gebeurtenis โ€œdata_receivedโ€ wordt geactiveerd, we de tekst โ€œdata_receivedโ€ naar de console moeten uitvoeren.
  4. Ten slotte hebben we een handmatige trigger van onze gebeurtenis met behulp van de functie eventEmiter.emit. Hierdoor wordt de gebeurtenis data_received geactiveerd.

Wanneer het programma wordt uitgevoerd, wordt de tekst โ€œdata ontvangenโ€ naar de console verzonden, zoals hieronder weergegeven.

Gebeurtenissen in Node.js

Gebeurtenissen uitzenden

Naast het uitzenden en ontvangen van gebeurtenissen, biedt Node.js extra methoden om te bepalen hoe luisteraars worden geregistreerd en gecontroleerd.

Bij het definiรซren van gebeurtenissen zijn er verschillende methoden voor gebeurtenissen die kunnen worden aangeroepen. Dit onderwerp richt zich op het in detail bekijken van elk van hen.

  1. Eenmalige gebeurtenishandlers

Soms bent u er wellicht alleen in geรฏnteresseerd om op een gebeurtenis te reageren als deze zich voor de eerste keer voordoet. In deze situaties kunt u de methode once() gebruiken.

Laten we eens kijken hoe we de eenmalige methode voor gebeurtenishandlers kunnen gebruiken.

Gebeurtenissen uitzenden

Code Uitleg:-

  1. Hier gebruiken we de 'once'-methode om te zeggen dat voor de gebeurtenis 'data_received' de callback-functie slechts รฉรฉn keer mag worden uitgevoerd.
  2. Hier activeren we handmatig de gebeurtenis 'data_received'.
  3. Wanneer de gebeurtenis 'data_received' opnieuw wordt geactiveerd, gebeurt er deze keer niets. Dit komt door de eerste stap waarin we zeiden dat de gebeurtenis maar รฉรฉn keer kon worden geactiveerd.

Als de code correct wordt uitgevoerd, is de uitvoer in het logboek 'data_received Successful'. Dit bericht verschijnt slechts รฉรฉn keer in de console.

  1. Gebeurtenislisteners inspecteren

Op elk moment in zijn levensduur kunnen aan een gebeurteniszender nul of meer luisteraars zijn gekoppeld. De luisteraars voor elk gebeurtenistype kunnen op verschillende manieren worden geรฏnspecteerd.

Als u alleen het aantal gekoppelde luisteraars wilt bepalen, hoeft u niet verder te zoeken dan de methode EventEmitter.listenerCount().

(Let op: Luisteraars zijn belangrijk omdat het hoofdprogramma moet weten of er luisteraars direct aan een evenement worden toegevoegd, anders werkt het programma niet goed omdat er extra luisteraars worden gebeld.)

Gebeurtenissen uitzenden

Code Uitleg:-

  1. We definiรซren een eventEmitter-type dat vereist is voor het gebruik van de gebeurtenisgerelateerde methoden.
  2. Vervolgens definiรซren we een object met de naam emitter dat zal worden gebruikt om onze gebeurtenishandlers te definiรซren.
  3. We maken twee gebeurtenishandlers die in principe niets doen. Dit wordt voor ons voorbeeld eenvoudig gehouden om te laten zien hoe de methode listenerCount werkt.
  4. Wanneer u nu de methode listenerCount aanroept voor onze gebeurtenis data_received, wordt het aantal gebeurtenislisteners verzonden dat aan deze gebeurtenis is gekoppeld in het consolelogboek.

Als de code correct wordt uitgevoerd, wordt de waarde 2 weergegeven in het consolelogboek.

  1. Het nieuweListener-evenement

Elke keer dat een nieuwe gebeurtenishandler wordt geregistreerd, verzendt de gebeurteniszender een newListener-gebeurtenis. Deze gebeurtenis wordt gebruikt om nieuwe gebeurtenishandlers te detecteren. Normaal gesproken gebruikt u de gebeurtenis newListener wanneer u bronnen moet toewijzen of een actie moet uitvoeren voor elke nieuwe gebeurtenishandler.

Gebeurtenissen uitzenden

var events = require('events');
var eventEmitter = events.EventEmitter;
var emitter = new eventEmitter();
emitter.on("newListener", function(eventName, listener) {
    console.log("Added listener for " + eventName + " events");
});
emitter.on('data_received', function() {});
emitter.on('data_received', function() {}); 

Code Uitleg:-

  1. We maken een nieuwe event handler voor de 'newListener'-gebeurtenis. Dus wanneer een nieuwe event handler wordt geregistreerd, wordt de tekst 'Added listener for' + de eventnaam weergegeven in de console.
  2. Hier schrijven we naar de console de tekst "Listener toegevoegd voor" + de gebeurtenisnaam voor elke geregistreerde gebeurtenis.
  3. We definiรซren 2 gebeurtenishandlers voor onze gebeurtenis 'data_received'.

Als de bovenstaande code correct wordt uitgevoerd, wordt de onderstaande tekst in de console weergegeven. Het laat alleen zien dat de gebeurtenishandler 'newListener' twee keer is geactiveerd.

Luisteraar toegevoegd voor data_received-gebeurtenissen

Luisteraar toegevoegd voor data_received-gebeurtenissen

Veelgestelde vragen

fs.readFile() laadt een volledig bestand in het geheugen voordat het wordt teruggegeven, terwijl fs.createReadStream() het bestand in kleine stukjes levert naarmate ze binnenkomen. Streams gebruiken veel minder geheugen voor grote bestanden en zorgen ervoor dat de verwerking kan beginnen voordat het lezen is voltooid.

Ja. Roep .pipe() รฉรฉn keer per bestemming aan, bijvoorbeeld readStream.pipe(bestemmingA) gevolgd door readStream.pipe(bestemmingB). Node.js levert dezelfde brokken aan elke bestemming, en elke beschrijfbare stream beheert nog steeds onafhankelijk zijn eigen backpressure.

Ja. Leid een leesbare stream door een transformatiestream zoals zlib.createGzip() en vervolgens naar een schrijfbare stream: readStream.pipe(zlib.createGzip()).pipe(writeStream). Het bestand wordt gecomprimeerd tijdens het streamen, zonder dat het hele bestand ooit in het geheugen wordt geladen.

Ze kunnen snel werkende readStream- en pipe-code opstellen, maar ze laten vaak de foutafhandeling en de backpressure-afhandeling weg. Controleer AI-gegenereerde streamcode altijd op deze hiaten voordat u deze in productie gebruikt.

Ze kunnen mogelijke oorzaken suggereren, zoals een ontbrekende drain-listener of een niet-afgehandelde foutgebeurtenis, maar om de werkelijke oorzaak te bevestigen is het nog steeds nodig om de streamstatus en applicatielogboeken handmatig te controleren.

Vat dit bericht samen met: