Node.js streamy: FileStream, Pipes a události

⚡ Chytré shrnutí

Streamy Node.js čtou a zapisují data po malých blocích, místo aby načítaly celé soubory do paměti. Tato stránka vysvětluje vytváření streamů souborů, řetězení kanálů, zpracování zpětného tlaku a model událostí, který zajišťuje efektivní přenos velkých dat.

  • 📖 Typy streamů: Node.js organizuje každý stream jako čitelný, zapisovatelný, duplexní nebo transformační a každé Node.js API vrací jeden z těchto čtyř typů.
  • 📂 Stream souborů: Funkce fs.createReadStream() a fs.createWriteStream() čtou a zapisují soubory po částech, aniž by se do paměti načítal celý soubor.
  • 🔗 Piping: Metoda pipe() propojuje čitelný proud přímo s zapisovatelným proudem a automaticky mezi nimi přesouvá data.
  • 🇧🇷 Protitlak: Funkce write() vrací hodnotu false, když je její vyrovnávací paměť plná, a událost drain signalizuje, kdy je bezpečné pokračovat v zápisu.
  • 📡 Streamované události: data, end, error a finish označují klíčové momenty v životním cyklu streamu a řídí většinu kódu pro práci se streamy.
  • Vlastní události: Modul events a EventEmitter umožňují jakékoli aplikaci Node.js definovat, generovat a naslouchat vlastním pojmenovaným událostem.
  • 🛠️ Ovládání posluchače: Funkce once(), listenerCount() a událost newListener řídí, kolikrát se obslužná rutina spustí a jak se posluchači běží. tracked.

Typy streamů v Node.js

Každý stream, který Node.js zpřístupňuje, spadá do jednoho ze čtyř základních typů a znalost typu, který dané API vrací, vysvětluje, jak se daný stream chová ještě před napsáním jakéhokoli kódu. Modul stream, který je součástí Node.js, definuje tyto kategorie a API pro soubory, sítě a kompresi používaná v tomto článku jsou na nich postavena.

Typ streamu Směr dat Příklad API Běžný případ použití
Readable Pouze zdroj (data vytékají) fs.createReadStream() Čtení souboru, příjem těla HTTP požadavku
Writable Pouze cíl (data proudí) fs.createWriteStream() Zápis souboru, odeslání HTTP odpovědi
Duplex Oba směry, nezávisle net.Socket (TCP soket) Obousměrná síťová komunikace
Změnit Oběma směry, data se během přenosu upravují zlib.createGzip() Komprese nebo kódování dat uprostřed kanálu

Čitelný proud a zapisovatelný proud jsou dva stavební bloky používané v tomto článku; oba také pohánějí HTTP webový server v Node.js. Duplexní stream kombinuje obě role v jednom objektu, zatímco transformační stream je duplexní stream, jehož výstup upravuje jeho vstup, například komprese gzip aplikovaná uprostřed streamu.

Filestream v Node.js

Po definování čtyř kategorií streamů zbytek tohoto článku vytvoří přenos souborů pomocí rozhraní API pro čtení a zapisování streamů.

Node široce využívá streamy jako mechanismus přenosu dat.

Když například pomocí funkce console.log něco odešlete do konzole, ve skutečnosti používáte proud k odeslání dat do konzole.

Node.js má také schopnost streamovat data ze souborů, aby je bylo možné správně číst a zapisovat. Nyní se podíváme na příklad toho, jak můžeme pomocí proudů číst a zapisovat ze souborů. Pro tento příklad musíme postupovat podle níže uvedených kroků

Krok 1) Vytvořte soubor s názvem data.txt, který obsahuje níže uvedená data. Předpokládejme, že tento soubor je uložen na jednotce D našeho místního počítače.

Výukový program na Node.js

Úvod

Události

Generators

Datová konektivita

Použití jasmínu

Krok 2) Napište příslušný kód, který bude využívat proudy ke čtení dat ze souboru.

Filestream v 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 Vysvětlení:-

  1. Nejprve musíme zahrnout moduly 'fs', které obsahují všechny funkce potřebné k vytváření streamů.
  2. Dále vytvoříme čitelný stream pomocí metody – createReadStream. Jako vstup uvedeme umístění našeho souboru data.txt.
  3. Funkce steam.on je obslužná rutina události a v ní zadáváme první parametr jako 'data'. To znamená, že kdykoli data přicházejí do proudu ze souboru, spustí se funkce zpětného volání. V našem případě definujeme funkci zpětného volání, která provede 2 základní kroky. První je převést data načtená ze souboru jako řetězec. Druhým by bylo odeslat převedený řetězec jako výstup do konzole.
  4. Bereme každý kus dat, který je načten z datového toku, a převádíme je na řetězec.
  5. Nakonec posíláme výstup každého převedeného chunku řetězce do konzole.

Výstup:

Filestream v Node.js

  • Pokud je kód proveden správně, uvidíte v konzole výše uvedený výstup. Tento výstup bude stejný jako v souboru data.txt.

Zápis do souboru

Stejným způsobem, jako když vytváříme čtený proud, můžeme vytvořit i zapisovací proud pro zápis dat do souboru. Nejprve vytvořte prázdný soubor bez obsahu s názvem data.txt. Předpokládejme, že tento soubor je umístěn na jednotce D našeho počítače.

Níže uvedený kód ukazuje, jak můžeme zapisovat data do souboru.

Filestream v 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 Vysvětlení:-

  1. Vytváříme zapisovatelný stream pomocí metody – createWriteStream. Jako vstup uvedeme umístění našeho souboru data.txt.
  2. Dále jsme použili metodu stream.write k zápisu různých řádků textu do našeho textového souboru. O zápis těchto dat do souboru data.txt se postará stream.

Pokud otevřete soubor data.txt, nyní v souboru uvidíte následující data

Výukový program na Node.js

Úvod

Události

Generators

Datová konektivita

Použití Jasmín

Trubky v Node.js

Nezávislé čtení a zápis streamů je užitečné, ale Node.js také umožňuje čitelnému streamu přímo se připojit k zapisovatelnému streamu a přesouvat data bez ručního ukládání kódu do vyrovnávací paměti.

V aplikacích Node lze proudy spojovat pomocí metody pipe(), která má dva argumenty:

  • Požadovaný zapisovatelný stream, který funguje jako cíl pro data a
  • Volitelný objekt používaný k předávání voleb.

Typický příklad použití rour, pokud chcete přenášet data z jednoho souboru do druhého.

Podívejme se tedy na příklad, jak můžeme přenášet data z jednoho souboru do druhého pomocí rour.

Krok 1) Vytvořte soubor s názvem datainput.txt, který obsahuje níže uvedená data. Předpokládejme, že tento soubor je uložen na jednotce D našeho místního počítače.

Výukový program na Node.js

Úvod

Události

Generators

Datová konektivita

Použití jasmínu

Krok 2) Vytvořte prázdný prázdný soubor s názvem dataOutput.txt a umístěte jej na jednotku D místního počítače.

Krok 3) Napište níže uvedený kód, abyste provedli přenos dat ze souboru datainput.txt do souboru dataOutput.txt.

Trubky v Node.js

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

Code Vysvětlení:-

  1. Nejprve vytváříme „readstream“ do našeho souboru datainput.txt, který obsahuje všechna naše data, která je třeba přenést do nového souboru.
  2. Potom musíme vytvořit „writestream“ do našeho souboru dataOutput.txt, což je náš prázdný soubor a je cílem pro přenos dat ze souboru datainput.txt.
  3. Poté použijeme příkaz roura k přenosu dat z readstreamu do streamu pro zápis. Příkaz roura převezme všechna data, která přicházejí do proudu čtení, a vloží je do proudu pro zápis.

Pokud nyní otevřete soubor dataOutput.txt, uvidíte všechna data, která byla přítomna v souboru datainput.txt.

Streamové události a protitlak v Node.js

Volání pipe(), jak je ukázáno výše, skrývá dva mechanismy, které jsou důležité ve velkém měřítku: události, které každý stream vysílá, a systém zpětného tlaku, který brání rychlému zdroji v zahlcení pomalého cíle.

  • údaje: vyzařováno čitelným proudem pokaždé, když je k dispozici nový blok.
  • konec: vydáno, jakmile čitelný stream doručí svůj poslední chunk.
  • Dokončit: vyzařované zapisovatelným proudem po vyprázdnění všech dat ve frontě.
  • chyba: vyzařováno libovolným typem proudu při selhání; vždy se s ním zachází, jinak proces zhroutí.

Níže uvedený příklad naslouchá všem čtyřem událostem a zároveň ručně spravuje protitlak, což je stejné chování, jaké pipe() poskytuje automaticky.

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

K zpětnému tlaku dochází, když čitelný proud vytváří data rychleji, než je zapisovatelný proud dokáže spotřebovat. Metoda write() vrací hodnotu false, jakmile se její vyrovnávací paměť zaplní, takže výše uvedený kód pozastaví zdroj, dokud cíl nevygeneruje událost drain, která signalizuje, že je bezpečné pokračovat. pipe() provede tento cyklus automaticky.

⚠ Tip: V produkčním prostředí upřednostňujte funkci pipeline() z modulu stream před ručním řetězcem pipe(), protože přeposílá chyby z každého streamu a automaticky zavírá popisovače souborů.

Události v Node.js

Streamy jsou jedním z příkladů širšího designu Node.js řízeného událostmi. Zbytek tohoto článku se zabývá tím, jak události fungují obecně, s využitím stejné třídy EventEmitter, na kterou se streamy interně spoléhají.

Události jsou jedním z klíčových konceptů v Node.js a někdy se Node.js označuje jako framework řízený událostmi.

Událost je v podstatě něco, co se stane. Pokud je například navázáno připojení k databázi, spustí se událost připojení k databázi. Programování řízené událostmi má vytvořit funkce, které budou spuštěny při spuštění konkrétních událostí.

Podívejme se na základní příklad definování události v Node.js.

Vytvoříme událost s názvem 'data_received'. Po spuštění této události bude do konzole odeslán text „data přijata“.

Události v 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 Vysvětlení:-

  1. K zahrnutí modulu 'události' použijte funkci vyžadovat. S tímto modulem budete moci vytvářet události v Node.js.
  2. Vytvořte nový emitor událostí. To se používá k navázání události, která je v našem případě „data_received“ na funkci zpětného volání, která je definována v kroku 3.
  3. Definujeme událostmi řízenou funkci, která říká, že pokud je spuštěna událost „data_received“, měli bychom do konzole vyslat text „data_received“.
  4. Konečně máme ruční spouštění naší události pomocí funkce eventEmiter.emit. Tím se spustí událost data_received.

Po spuštění programu se do konzole odešle text „data přijata“, jak je znázorněno níže.

Události v Node.js

Vysílání událostí

Kromě vysílání a naslouchání událostem nabízí Node.js další metody pro řízení registrace a kontroly posluchačů.

Při definování událostí existují různé metody pro události, které lze vyvolat. Toto téma se zaměřuje na podrobný pohled na každý z nich.

  1. Jednorázové manipulátory událostí

Někdy můžete mít zájem reagovat na událost pouze tehdy, když k ní dojde poprvé. V těchto situacích můžete použít metodu Once().

Podívejme se, jak můžeme využít metodu Once pro obsluhu událostí.

Vysílání událostí

Code Vysvětlení:-

  1. Zde používáme metodu 'once', abychom řekli, že pro událost 'data_received' by funkce zpětného volání měla být provedena pouze jednou.
  2. Zde ručně spouštíme událost 'data_received'.
  3. Když se událost 'data_received' znovu spustí, tentokrát se nic nestane. Je to kvůli prvnímu kroku, kdy jsme řekli, že událost může být spuštěna pouze jednou.

Pokud je kód proveden správně, výstup v protokolu bude 'data_received success'. Tato zpráva se v konzole objeví pouze jednou.

  1. Kontrola posluchačů událostí

V kterémkoli okamžiku své životnosti může mít emitor události připojeno nula nebo více posluchačů. Posluchače pro každý typ události lze kontrolovat několika způsoby.

Pokud vás zajímá pouze určení počtu připojených posluchačů, pak nehledejte nic jiného než metodu EventEmitter.listenerCount().

(Poznámka: Posluchači jsou důležití, protože hlavní program by měl vědět, zda jsou posluchači přidáváni k události, jinak program nebude fungovat, protože budou voláni další posluchači.)

Vysílání událostí

Code Vysvětlení:-

  1. Definujeme typ eventEmitter, který je vyžadován pro použití metod souvisejících s událostmi.
  2. Potom definujeme objekt nazvaný emitor, který bude použit k definování našich obslužných rutin událostí.
  3. Vytváříme 2 handlery událostí, které v podstatě nic nedělají. Pro náš příklad je to jednoduché, abychom ukázali, jak metoda listenerCount funguje.
  4. Když nyní vyvoláte metodu listenerCount v naší události data_received, odešle počet posluchačů události připojených k této události v protokolu konzoly.

Pokud je kód proveden správně, v protokolu konzoly se zobrazí hodnota 2.

  1. NewListener Event

Pokaždé, když je registrována nová obsluha události, emitor události vyšle událost newListener. Tato událost se používá k detekci nových obslužných rutin událostí. Událost newListener obvykle používáte, když potřebujete přidělit prostředky nebo provést nějakou akci pro každou novou obsluhu události.

Vysílání událostí

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 Vysvětlení:-

  1. Vytváříme novou obsluhu události pro událost 'newListener'. Kdykoli se tedy zaregistruje nová obsluha události, v konzole se zobrazí text „Přidán posluchač pro“ + název události.
  2. Zde zapisujeme do konzole text „Přidaný posluchač pro“ + název události pro každou registrovanou událost.
  3. Definujeme 2 obslužné rutiny událostí pro naši událost 'data_received'.

Pokud je výše uvedený kód proveden správně, zobrazí se v konzole níže uvedený text. To jen ukazuje, že obslužná rutina události 'newListener' byla spuštěna dvakrát.

Přidán posluchač pro události data_received

Přidán posluchač pro události data_received

Nejčastější dotazy

Funkce fs.readFile() načte celý soubor do paměti a poté jej vrátí, zatímco funkce fs.createReadStream() doručí soubor po malých částech, jakmile dorazí. Streamy spotřebují pro velké soubory mnohem méně paměti a umožňují zahájení zpracování před dokončením čtení.

Ano. Volejte .pipe() jednou pro každý cíl, například readStream.pipe(destinationA) a poté readStream.pipe(destinationB). Node.js doručuje stejné bloky dat do každého cíle a každý zapisovatelný stream si stále nezávisle spravuje svůj vlastní protitlak.

Ano. Proveďte čitelný stream přes transformační stream, jako je například zlib.createGzip(), a poté do zapisovatelného streamu: readStream.pipe(zlib.createGzip()).pipe(writeStream). Soubor se během streamování komprimuje, aniž by se celý soubor načetl do paměti.

Dokážou rychle navrhnout funkční readStream a zprovoznit standardizovanou verzi, ale často vynechávají obslužnou rutinu chybových událostí a zpracování zpětného tlaku. Před použitím v produkčním prostředí vždy zkontrolujte kód streamu generovaný umělou inteligencí, zda v obou případech nedochází k chybám.

Mohou naznačovat pravděpodobné příčiny, jako je chybějící posluchač odtoku nebo neošetřená chybová událost, ale potvrzení skutečné příčiny stále vyžaduje ruční kontrolu stavu streamu a protokolů aplikace.

Shrňte tento příspěvek takto: