Node.js streamovi: FileStream, Pipes i događaji

⚡ Pametni sažetak

Node.js streamovi čitaju i pišu podatke u malim komadima umjesto da učitavaju cijele datoteke u memoriju, a ova stranica objašnjava stvaranje datotečnog toka, ulančavanje cijevi, rukovanje povratnim pritiskom i model događaja koji održava učinkovitim prijenos velikih količina podataka.

  • ???? Vrste streamova: Node.js organizira svaki stream kao čitljiv, pisiv, dupleks ili transformacijski, a svaki Node.js API vraća jedan od ova četiri tipa.
  • 📂 Tok datoteka: fs.createReadStream() i fs.createWriteStream() čitaju i pišu datoteke u dijelovima bez učitavanja cijele datoteke u memoriju.
  • 🔗 Piping: Metoda pipe() izravno povezuje čitljivi stream s streamom u koji se može pisati i automatski premješta podatke između njih.
  • ⚖️ Protutlak: write() vraća false kada je međuspremnik pun, a događaj drain signalizira kada je sigurno nastaviti pisanje.
  • 📡 Događaji streama: data, end, error i finish označavaju ključne trenutke u životnom ciklusu streama i pokreću većinu koda za rukovanje streamom.
  • Prilagođeni događaji: Modul događaja i EventEmitter omogućuju bilo kojoj Node.js aplikaciji definiranje, emitiranje i slušanje vlastitih imenovanih događaja.
  • 🛠️ Kontrola slušatelja: once(), listenerCount() i događaj newListener upravljaju brojem pokretanja rukovatelja i načinom rada slušatelja. tracked

Vrste streamova u Node.js-u

Svaki stream koji Node.js izlaže spada u jedan od četiri osnovna tipa, a poznavanje tipa koji određeni API vraća objašnjava kako se taj stream ponaša prije nego što se napiše bilo kakav kod. Modul streama koji dolazi s Node.js-om definira ove kategorije, a API-ji za datoteke, mrežu i kompresiju korišteni u ovom članku izgrađeni su na njima.

Vrsta toka Smjer podataka Primjer API-ja Uobičajeni slučaj upotrebe
Readable Samo izvor (podaci izlaze) fs.createReadStream() Čitanje datoteke, primanje tijela HTTP zahtjeva
Može se pisati Samo odredište (podaci ulaze) fs.createWriteStream() Pisanje datoteke, slanje HTTP odgovora
Dvoetažni Oba smjera, neovisno net.Socket (TCP utičnica) Dvosmjerna mrežna komunikacija
Transformirati Oba smjera, podaci se mijenjaju tijekom prijenosa zlib.createGzip() Komprimiranje ili kodiranje podataka usred prijenosa

Čitljivi stream i stream u koji se može pisati dva su gradiva koja se koriste u ovom članku; oba također pokreću HTTP web poslužitelj u Node.js-u. Dvostruki stream kombinira obje uloge u jednom objektu, dok je transformacijski stream dvostruki stream čiji izlaz modificira ulaz, poput gzip kompresije primijenjene usred streama.

Filestream u Node.js

Nakon što su definirane četiri kategorije streamova, ostatak ovog članka izrađuje prijenos datoteka pomoću API-ja streamova za čitanje i pisanje.

Node u velikoj mjeri koristi streamove kao mehanizam prijenosa podataka.

Na primjer, kada ispisujete bilo što na konzolu pomoću funkcije console.log, zapravo koristite tok za slanje podataka na konzolu.

Node.js također ima mogućnost strujanja podataka iz datoteka tako da se mogu čitati i pisati na odgovarajući način. Sada ćemo pogledati primjer kako možemo koristiti tokove za čitanje i pisanje iz datoteka. Moramo slijediti dolje navedene korake za ovaj primjer

Korak 1) Napravite datoteku pod nazivom data.txt koja sadrži podatke ispod. Pretpostavimo da je ova datoteka pohranjena na D disku našeg lokalnog stroja.

Vodič za Node.js

Uvod

Događaji

Generators

Podatkovna povezanost

Korištenje Jasmina

Korak 2) Napišite odgovarajući kod koji će koristiti streamove za čitanje podataka iz datoteke.

Filestream u 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 Obrazloženje:-

  1. Najprije moramo uključiti 'fs' module koji sadrže sve funkcije potrebne za stvaranje tokova.
  2. Zatim stvaramo čitljiv stream pomoću metode – createReadStream. Kao unos dajemo lokaciju naše datoteke data.txt.
  3. Funkcija steam.on je rukovatelj događajima iu njoj navodimo prvi parametar kao 'podatke'. To znači da svaki put kada podaci dođu u tok iz datoteke, izvršite funkciju povratnog poziva. U našem slučaju, definiramo funkciju povratnog poziva koja će izvršiti 2 osnovna koraka. Prvi je pretvoriti podatke pročitane iz datoteke u niz. Drugi bi bio slanje pretvorenog niza kao izlaz na konzolu.
  4. Uzimamo svaki dio podataka koji se čita iz toka podataka i pretvaramo ga u niz.
  5. Konačno, šaljemo izlaz svakog niza pretvorenog dijela na konzolu.

Izlaz:

Filestream u Node.js

  • Ako se kôd ispravno izvede, vidjet ćete gornji izlaz u konzoli. Ovaj izlaz će biti isti kao onaj u datoteci data.txt.

Zapisivanje u datoteku

Na isti način na koji stvaramo tok za čitanje, možemo također stvoriti tok za pisanje za pisanje podataka u datoteku. Kreirajmo prvo praznu datoteku bez sadržaja pod nazivom data.txt. Pretpostavimo da je ova datoteka smještena na D pogon našeg računala.

Donji kod pokazuje kako možemo pisati podatke u datoteku.

Filestream u 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 Obrazloženje:-

  1. Kreiramo tok koji se može pisati korištenjem metode – createWriteStream. Kao unos dajemo lokaciju naše datoteke data.txt.
  2. Zatim smo upotrijebili metodu stream.write za pisanje različitih redaka teksta u našu tekstualnu datoteku. Stream će se pobrinuti za pisanje ovih podataka u datoteku data.txt.

Ako otvorite datoteku data.txt, vidjet ćete sljedeće podatke u datoteci

Vodič za Node.js

Uvod

Događaji

Generators

Podatkovna povezanost

Korištenje Jasmin

Cijevi u Node.js

Neovisno čitanje i pisanje streamova je korisno, ali Node.js također omogućuje izravno povezivanje čitljivog streama s streamom za pisanje, premještajući podatke bez ručnog međuspremničkog koda.

Unutar Node aplikacija, tokovi se mogu spojiti zajedno pomoću metode pipe(), koja uzima dva argumenta:

  • Potreban tok za pisanje koji služi kao odredište za podatke i
  • Neobavezni objekt koji se koristi za prosljeđivanje opcija.

Tipičan primjer korištenja cijevi, ako želite prenijeti podatke iz jedne datoteke u drugu.

Dakle, pogledajmo primjer kako možemo prenijeti podatke iz jedne datoteke u drugu pomoću cijevi.

Korak 1) Napravite datoteku pod nazivom datainput.txt koja sadrži podatke u nastavku. Pretpostavimo da je ova datoteka pohranjena na D disku našeg lokalnog stroja.

Vodič za Node.js

Uvod

Događaji

Generators

Podatkovna povezanost

Korištenje Jasmina

Korak 2) Napravite praznu praznu datoteku pod nazivom dataOutput.txt i stavite je na D pogon vašeg lokalnog računala.

Korak 3) Napišite kod u nastavku da biste izvršili prijenos podataka iz datoteke datainput.txt u datoteku dataOutput.txt.

Cijevi u Node.js

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

Code Obrazloženje:-

  1. Prvo stvaramo "readstream" za našu datainput.txt datoteku koja sadrži sve naše podatke koje je potrebno prenijeti u novu datoteku.
  2. Zatim trebamo stvoriti "writestream" u našu datoteku dataOutput.txt, koja je naša prazna datoteka i odredište je za prijenos podataka iz datoteke datainput.txt.
  3. Zatim koristimo naredbu pipe za prijenos podataka iz toka čitanja u tok pisanja. Naredba cijevi će uzeti sve podatke koji dolaze u tok čitanja i gurnuti ih u tok pisanja.

Ako sada otvorite datoteku dataOutput.txt, vidjet ćete sve podatke koji su bili prisutni u datoteci dataOutput.txt.

Događaji streama i povratni tlak u Node.js-u

Pozivanje pipe(), kao što je gore prikazano, skriva dva mehanizma koja su važna u velikim razmjerima: događaje koje svaki stream emitira i sustav povratnog tlaka koji sprječava brzi izvor da preoptereti sporo odredište.

  • podatke: emitirano od strane čitljivog streama svaki put kada je dostupan novi blok.
  • kraj: emitira se nakon što čitljivi stream isporuči svoj posljednji dio.
  • Završi: emitira se putem zapisivog streama nakon što se svi podaci u redu čekanja ispišu.
  • pogreška: emitira ga bilo koji tip streama u slučaju kvara; uvijek ga obradi ili se proces ruši.

Primjer u nastavku osluškuje sva četiri događaja dok ručno upravlja povratnim tlakom, isto ponašanje koje pipe() pruža automatski.

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);
});

Protivpritisak se javlja kada čitljivi stream proizvodi podatke brže nego što ih stream za pisanje može potrošiti. Metoda write() vraća false nakon što se međuspremnik napuni, pa gornji kod pauzira izvor dok odredište ne emitira događaj drain, signalizirajući da je sigurno nastaviti. pipe() automatski izvršava ovaj ciklus.

⚠ Savjet: U produkciji, preferirajte funkciju pipeline() iz modula stream u odnosu na ručni lanac pipe(), jer ona prosljeđuje pogreške iz svakog streama i automatski zatvara ručke datoteka.

Događaji u Node.js

Streamovi su jedan primjer šireg dizajna vođenog događajima u Node.js-u. Ostatak ovog članka razmatra kako događaji općenito funkcioniraju, koristeći istu klasu EventEmitter na koju se streamovi interno oslanjaju.

Događaji su jedan od ključnih koncepata u Node.js i ponekad se Node.js naziva okvirom vođenim događajima.

U osnovi, događaj je nešto što se događa. Na primjer, ako se uspostavi veza s bazom podataka, tada se pokreće događaj povezivanja s bazom podataka. Programiranje vođeno događajima je stvaranje funkcija koje će se pokrenuti kada se pokrenu određeni događaji.

Pogledajmo osnovni primjer definiranja događaja u Node.js.

Kreirat ćemo događaj pod nazivom 'data_received'. Kada se ovaj događaj pokrene, tekst "podaci su primljeni" bit će poslan na konzolu.

Događaji u 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 Obrazloženje:-

  1. Upotrijebite funkciju require za uključivanje modula 'događaji'. Pomoću ovog modula moći ćete kreirati događaje u Node.js.
  2. Stvorite novi emiter događaja. Ovo se koristi za vezanje događaja, koji je u našem slučaju "data_received" na funkciju povratnog poziva koja je definirana u koraku 3.
  3. Definiramo funkciju vođenu događajima koja kaže da ako se pokrene događaj "data_received", tada bismo trebali poslati tekst "data_received" na konzolu.
  4. Konačno, imamo ručni okidač našeg događaja pomoću funkcije eventEmiter.emit. To će pokrenuti događaj data_received.

Kada se program pokrene, tekst "podaci su primljeni" bit će poslan na konzolu kao što je prikazano u nastavku.

Događaji u Node.js

Emitirajući događaji

Osim emitiranja i slušanja događaja, Node.js nudi dodatne metode za kontrolu načina registracije i pregleda slušatelja.

Prilikom definiranja događaja postoje različite metode za događaje koje je moguće pozvati. Ova se tema fokusira na detaljno razmatranje svakog od njih.

  1. Rukovatelji jednokratnim događajima

Ponekad ćete možda biti zainteresirani reagirati na događaj tek kad se prvi put dogodi. U tim situacijama možete koristiti metodu once().

Pogledajmo kako se možemo poslužiti jednom metodom za rukovatelje događajima.

Emitirajući događaji

Code Obrazloženje:-

  1. Ovdje koristimo metodu 'jednom' kako bismo rekli da se za događaj 'data_received' funkcija povratnog poziva treba izvršiti samo jednom.
  2. Ovdje ručno pokrećemo događaj 'data_received'.
  3. Kada se događaj 'data_received' ponovo pokrene, ovaj put se ništa neće dogoditi. To je zbog prvog koraka u kojem smo rekli da se događaj može pokrenuti samo jednom.

Ako se kôd ispravno izvede, izlaz u dnevniku bit će "data_received successfully". Ova poruka će se pojaviti samo jednom u konzoli.

  1. Provjera slušatelja događaja

U bilo kojem trenutku svog životnog vijeka emiter događaja može imati nula ili više slušatelja pridruženih sebi. Slušatelji za svaku vrstu događaja mogu se pregledati na nekoliko načina.

Ako vas zanima samo određivanje broja priloženih slušatelja, ne tražite dalje od metode EventEmitter.listenerCount().

(Bilješka: Slušatelji su važni jer bi glavni program trebao znati dodaju li se slušatelji u hodu u događaj, inače će program pokvariti jer će biti pozvani dodatni slušatelji.)

Emitirajući događaji

Code Obrazloženje:-

  1. Definiramo tip eventEmitter koji je potreban za korištenje metoda povezanih s događajima.
  2. Zatim definiramo objekt koji se zove emiter koji će se koristiti za definiranje naših rukovatelja događajima.
  3. Stvaramo 2 rukovatelja događajima koji u osnovi ne rade ništa. Ovo je jednostavno za naš primjer samo da pokaže kako metoda listenerCount radi.
  4. Sada kada pozovete metodu listenerCount na našem događaju data_received, ona će poslati broj slušatelja događaja priloženih ovom događaju u zapisnik konzole.

Ako se kôd ispravno izvede, vrijednost 2 bit će prikazana u zapisniku konzole.

  1. Događaj newListener

Svaki put kada se registrira novi rukovatelj događajima, emiter događaja emitira događaj newListener. Ovaj se događaj koristi za otkrivanje novih rukovatelja događajima. Događaj newListener obično koristite kada trebate dodijeliti resurse ili izvršiti neku radnju za svaki novi rukovatelj događajima.

Emitirajući događaji

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 Obrazloženje:-

  1. Stvaramo novi rukovatelj događajima za događaj 'newListener'. Dakle, kad god se registrira novi rukovatelj događajima, na konzoli će se prikazati tekst "Dodan slušatelj za" + naziv događaja.
  2. Ovdje upisujemo na konzolu tekst “Dodan slušatelj za” + naziv događaja za svaki registrirani događaj.
  3. Definiramo 2 rukovatelja događajima za naš događaj 'data_received'.

Ako se gornji kod ispravno izvede, tekst u nastavku bit će prikazan na konzoli. To samo pokazuje da je rukovatelj događajem 'newListener' dvaput pokrenut.

Dodan slušatelj za data_received događaje

Dodan slušatelj za data_received događaje

Pitanja i odgovori

fs.readFile() učitava cijelu datoteku u memoriju prije nego što je vrati, dok fs.createReadStream() isporučuje datoteku u malim dijelovima kako stižu. Streamovi koriste puno manje memorije za velike datoteke i dopuštaju da obrada započne prije nego što se čitanje završi.

Da. Pozovite .pipe() jednom po odredištu, kao što je readStream.pipe(destinationA) nakon čega slijedi readStream.pipe(destinationB). Node.js isporučuje iste dijelove na svako odredište, a svaki zapisivi stream i dalje samostalno upravlja vlastitim povratnim tlakom.

Da. Proslijedite čitljiv stream kroz transformacijski stream kao što je zlib.createGzip(), a zatim u stream za pisanje: readStream.pipe(zlib.createGzip()).pipe(writeStream). Datoteka se komprimira tijekom strujanja, bez učitavanja cijele datoteke u memoriju.

Mogu brzo izraditi radni readStream i proslijediti standardni kod, ali često izostavljaju rukovatelj događajima pogrešaka i rukovanje povratnim pritiskom. Uvijek pregledajte kod streama generiran umjetnom inteligencijom za oba nedostatka prije upotrebe u produkciji.

Mogu predložiti vjerojatne uzroke, poput nedostajućeg slušača odvoda ili neobrađenog događaja pogreške, ali potvrđivanje pravog uzroka i dalje zahtijeva ručnu provjeru stanja streama i logova aplikacije.

Sažmite ovu objavu uz: