Strumienie Node.js: strumienie plików, potoki i zdarzenia
⚡ Inteligentne podsumowanie
Strumienie Node.js odczytują i zapisują dane w małych porcjach zamiast ładować całe pliki do pamięci. Na tej stronie opisano tworzenie strumieni plików, łączenie potoków, obsługę przeciwciśnienia i model zdarzeń, który zapewnia wydajność przesyłania danych na dużą skalę.

Typy strumieni w Node.js
Każdy strumień udostępniany przez Node.js należy do jednego z czterech typów bazowych, a wiedza o tym, jaki typ zwraca dane API, wyjaśnia, jak ten strumień się zachowuje, zanim zostanie napisany jakikolwiek kod. Moduł strumienia dostarczany z Node.js definiuje te kategorie, a interfejsy API plików, sieci i kompresji używane w tym artykule są zbudowane na ich bazie.
| Typ strumienia | Kierunek danych | Przykładowe API | Typowy przypadek użycia |
|---|---|---|---|
| Readable | Tylko źródło (wypływ danych) | fs.createReadStream() | Odczyt pliku, odbieranie treści żądania HTTP |
| Zapisu | Tylko miejsce docelowe (przepływ danych) | fs.createWriteStream() | Zapisywanie pliku, wysyłanie odpowiedzi HTTP |
| dupleks | W obu kierunkach, niezależnie | net.Socket (gniazdo TCP) | Dwukierunkowa komunikacja sieciowa |
| Przekształcać | W obu kierunkach dane są modyfikowane w trakcie przesyłu | zlib.createGzip() | Kompresja lub kodowanie danych w trakcie procesu |
Strumień czytelny i strumień zapisywalny to dwa podstawowe bloki używane w tym artykule; oba zasilają również Serwer WWW HTTP w Node.js. Strumień dupleksowy łączy obie role w jednym obiekcie, podczas gdy strumień transformacji to strumień dupleksowy, którego dane wyjściowe modyfikują dane wejściowe, np. kompresja gzip stosowana w trakcie strumienia.
Strumień plików w Node.js
Po zdefiniowaniu czterech kategorii strumieni, dalsza część artykułu będzie poświęcona tworzeniu transferu plików przy użyciu interfejsów API strumieni do odczytu i zapisu.
Węzeł szeroko wykorzystuje strumienie jako mechanizm przesyłania danych.
Na przykład, gdy wysyłasz cokolwiek do konsoli za pomocą funkcji console.log, w rzeczywistości używasz strumienia do wysyłania danych do konsoli.
Node.js ma także możliwość strumieniowego przesyłania danych z plików, dzięki czemu można je odpowiednio odczytać i zapisać. Przyjrzymy się teraz przykładowi wykorzystania strumieni do odczytu i zapisu plików. W tym przykładzie musimy wykonać poniższe kroki
Krok 1) Utwórz plik o nazwie data.txt zawierający poniższe dane. Załóżmy, że plik ten jest przechowywany na dysku D naszej lokalnej maszyny.
Samouczek dotyczący Node.js
Wprowadzenie
Wydarzenia
Łączność danych
Używanie jaśminu
Krok 2) Napisz odpowiedni kod, który będzie korzystał ze strumieni do odczytania danych z pliku.
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 Wyjaśnienie:-
- Najpierw musimy dołączyć moduły „fs”, które zawierają całą funkcjonalność wymaganą do tworzenia strumieni.
- Następnie tworzymy czytelny strumień, używając metody – createReadStream. Jako dane wejściowe podajemy lokalizację naszego pliku data.txt.
- Funkcja steam.on jest obsługą zdarzeń i w niej określamy pierwszy parametr jako „data”. Oznacza to, że za każdym razem, gdy dane napływają do strumienia z pliku, należy wykonać funkcję wywołania zwrotnego. W naszym przypadku definiujemy funkcję wywołania zwrotnego, która wykona 2 podstawowe kroki. Pierwsza polega na konwersji danych odczytanych z pliku na ciąg znaków. Drugim byłoby wysłanie przekonwertowanego ciągu jako danych wyjściowych do konsoli.
- Pobieramy każdy fragment danych odczytany ze strumienia danych i konwertujemy go na ciąg znaków.
- Na koniec wysyłamy dane wyjściowe każdego fragmentu skonwertowanego ciągu znaków do konsoli.
Wyjście:
- Jeśli kod zostanie wykonany poprawnie, w konsoli zobaczysz powyższe dane wyjściowe. Dane wyjściowe będą takie same, jak w pliku data.txt.
Zapis do pliku
W ten sam sposób, w jaki tworzymy strumień odczytu, możemy również utworzyć strumień zapisu do zapisu danych do pliku. Najpierw utwórzmy pusty plik bez zawartości o nazwie data.txt. Załóżmy, że plik ten znajduje się na dysku D naszego komputera.
Poniższy kod pokazuje w jaki sposób możemy zapisać dane do pliku.
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 Wyjaśnienie:-
- Tworzymy zapisywalny strumień metodą createWriteStream. Jako dane wejściowe podajemy lokalizację naszego pliku data.txt.
- Następnie użyliśmy metody stream.write do zapisania różnych linii tekstu do naszego pliku tekstowego. Strumień zajmie się zapisaniem tych danych do pliku data.txt.
Jeśli otworzysz plik data.txt, zobaczysz teraz w pliku następujące dane
Samouczek dotyczący Node.js
Wprowadzenie
Wydarzenia
Generators
Łączność danych
Korzystanie z Jaśmin
Potoki w Node.js
Możliwość niezależnego odczytywania i zapisywania strumieni jest przydatna, ale Node.js pozwala również na bezpośrednie połączenie strumienia czytelnego ze strumieniem zapisywalnym, co pozwala na przesyłanie danych bez konieczności ręcznego buforowania kodu.
W aplikacjach Node strumienie można łączyć za pomocą metody pipe(), która przyjmuje dwa argumenty:
- Wymagany zapisywalny strumień, który działa jako miejsce docelowe danych i
- Opcjonalny obiekt używany do przekazywania opcji.
Typowy przykład użycia potoków, jeśli chcesz przenieść dane z jednego pliku do drugiego.
Zobaczmy więc przykład, jak możemy przenieść dane z jednego pliku do drugiego za pomocą potoków.
Krok 1) Utwórz plik o nazwie datainput.txt zawierający poniższe dane. Załóżmy, że plik ten jest przechowywany na dysku D naszej lokalnej maszyny.
Samouczek dotyczący Node.js
Wprowadzenie
Wydarzenia
Generators
Łączność danych
Używanie jaśminu
Krok 2) Utwórz pusty plik o nazwie dataOutput.txt i umieść go na dysku D komputera lokalnego.
Krok 3) Napisz poniższy kod realizujący transfer danych z pliku datainput.txt do pliku dataOutput.txt.
var fs = require("fs"); var readStream = fs.createReadStream("D://datainput.txt"); var writeStream = fs.createWriteStream("D://dataOutput.txt"); readStream.pipe(writeStream);
Code Wyjaśnienie:-
- Najpierw tworzymy „readstream” do naszego pliku datainput.txt, który zawiera wszystkie nasze dane, które należy przenieść do nowego pliku.
- Następnie musimy utworzyć „writestream” do naszego pliku dataOutput.txt, który jest naszym pustym plikiem i jest miejscem docelowym przesyłania danych z pliku datainput.txt.
- Następnie używamy polecenia potoku, aby przesłać dane ze strumienia odczytu do strumienia zapisu. Polecenie potok pobierze wszystkie dane, które przyjdą do strumienia odczytu i wypchnie je do strumienia zapisu.
Jeśli teraz otworzysz plik dataOutput.txt, zobaczysz wszystkie dane, które znajdowały się w pliku datainput.txt.
Zdarzenia strumieniowe i presja zwrotna w Node.js
Wywołanie pipe(), jak pokazano powyżej, ukrywa dwie mechaniki, które mają znaczenie na dużą skalę: zdarzenia emitowane przez każdy strumień oraz system przeciwciśnienia, który zapobiega przytłoczeniu wolnego celu przez szybkie źródło.
- Dane: emitowane przez czytelny strumień za każdym razem, gdy dostępny jest nowy fragment.
- koniec: emitowane, gdy czytelny strumień dostarczy swój ostatni fragment.
- koniec: emitowane przez zapisywalny strumień po opróżnieniu wszystkich danych w kolejce.
- błąd: emitowane przez dowolny typ strumienia w przypadku awarii; zawsze obsługuj tę sytuację, w przeciwnym razie proces ulegnie awarii.
Poniższy przykład nasłuchuje wszystkich czterech zdarzeń, jednocześnie ręcznie zarządzając ciśnieniem wstecznym, czyli tak samo, jak pipe() zapewnia to automatycznie.
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); });
Zjawisko backpressure występuje, gdy strumień czytelny generuje dane szybciej, niż strumień zapisywalny może je skonsumować. Metoda write() zwraca false po zapełnieniu bufora, więc powyższy kod wstrzymuje źródło do momentu, aż źródło wyemituje zdarzenie drenażu, sygnalizując, że można je bezpiecznie wznowić. Metoda pipe() wykonuje ten cykl automatycznie.
⚠ Wskazówka: W środowisku produkcyjnym należy preferować funkcję pipeline() z modułu strumieniowego zamiast ręcznego łańcucha pipe(), ponieważ przekazuje ona błędy z każdego strumienia i automatycznie zamyka uchwyty plików.
Zdarzenia w Node.js
Strumienie są jednym z przykładów szerszego podejścia Node.js do projektowania opartego na zdarzeniach. W dalszej części artykułu przyjrzymy się, jak działają zdarzenia, wykorzystując tę samą klasę EventEmitter, z której wewnętrznie korzystają strumienie.
Zdarzenia stanowią jedną z kluczowych koncepcji Node.js i czasami Node.js jest określany jako framework oparty na zdarzeniach.
Zasadniczo wydarzenie to coś, co się dzieje. Na przykład, jeśli zostanie nawiązane połączenie z bazą danych, wyzwalane jest zdarzenie połączenia z bazą danych. Programowanie sterowane zdarzeniami polega na tworzeniu funkcji, które będą uruchamiane w przypadku wyzwolenia określonych zdarzeń.
Spójrzmy na podstawowy przykład definiowania zdarzenia w Node.js.
Stworzymy wydarzenie o nazwie „data_received”. Po wywołaniu tego zdarzenia do konsoli zostanie wysłany tekst „dane otrzymane”.
var events = require('events'); var eventEmitter = new events.EventEmitter(); eventEmitter.on('data_received', function() { console.log('data received succesfully.'); }); eventEmitter.emit('data_received');
Code Wyjaśnienie:-
- Użyj funkcji require, aby uwzględnić moduł „zdarzenia”. Dzięki temu modułowi będziesz mógł tworzyć zdarzenia w Node.js.
- Utwórz nowy emiter zdarzeń. Służy do powiązania zdarzenia, którym w naszym przypadku jest „data_received” z funkcją wywołania zwrotnego zdefiniowaną w kroku 3.
- Definiujemy funkcję sterowaną zdarzeniami, która mówi, że jeśli w przypadku wywołania zdarzenia „data_received” powinniśmy wypisać na konsolę tekst „data_received”.
- Wreszcie mamy ręczne wyzwalanie naszego zdarzenia za pomocą funkcji eventEmiter.emit. Spowoduje to wyzwolenie zdarzenia data_received.
Po uruchomieniu programu do konsoli zostanie wysłany tekst „dane otrzymane”, jak pokazano poniżej.
Emisja zdarzeń
Oprócz emitowania i nasłuchiwania zdarzeń Node.js udostępnia dodatkowe metody kontrolujące sposób rejestrowania i sprawdzania obiektów nasłuchujących.
Podczas definiowania zdarzeń istnieją różne metody wywoływania zdarzeń. W tym temacie skupimy się na szczegółowym omówieniu każdego z nich.
- Jednorazowe procedury obsługi zdarzeń
Czasami możesz chcieć zareagować na wydarzenie dopiero za pierwszym razem, gdy ono nastąpi. W takich sytuacjach można użyć metody Once().
Zobaczmy, jak możemy wykorzystać metodę Once do obsługi zdarzeń.
Code Wyjaśnienie:-
- Tutaj używamy metody „once”, aby powiedzieć, że w przypadku zdarzenia „data_received” funkcja wywołania zwrotnego powinna zostać wykonana tylko raz.
- Tutaj ręcznie wyzwalamy zdarzenie „data_received”.
- Gdy zdarzenie „data_received” zostanie ponownie wywołane, tym razem nic się nie stanie. Dzieje się tak ze względu na pierwszy krok, w którym powiedzieliśmy, że zdarzenie może zostać wywołane tylko raz.
Jeśli kod zostanie wykonany poprawnie, w dzienniku pojawi się komunikat „data_received pomyślnie”. Komunikat ten pojawi się w konsoli tylko raz.
- Sprawdzanie detektorów zdarzeń
W dowolnym momencie swojego istnienia do emitera zdarzeń może być podłączonych zero lub więcej słuchaczy. Elementy nasłuchujące dla każdego typu zdarzenia można sprawdzić na kilka sposobów.
Jeśli interesuje Cię tylko określenie liczby podłączonych słuchaczy, nie szukaj dalej niż metoda EventEmitter.listenerCount().
(Uwaga: Słuchacze są ważni, ponieważ główny program powinien wiedzieć, czy słuchacze są dodawani na bieżąco do wydarzenia, w przeciwnym razie program będzie działać nieprawidłowo, ponieważ zostaną wywołani dodatkowi słuchacze.)
Code Wyjaśnienie:-
- Definiujemy typ eventEmitter, który jest wymagany do korzystania z metod związanych ze zdarzeniami.
- Następnie definiujemy obiekt zwany emiterem, który będzie używany do definiowania naszych procedur obsługi zdarzeń.
- Tworzymy 2 procedury obsługi zdarzeń, które w zasadzie nic nie robią. W naszym przykładzie jest to proste, aby pokazać, jak działa metoda ListenerCount.
- Teraz, gdy wywołasz metodę ListenerCount dla naszego zdarzenia data_received, wyśle ona liczbę detektorów zdarzeń dołączonych do tego zdarzenia w dzienniku konsoli.
Jeżeli kod zostanie wykonany poprawnie, w logu konsoli pojawi się wartość 2.
- Wydarzenie nowego słuchacza
Za każdym razem, gdy rejestrowana jest nowa procedura obsługi zdarzenia, emiter zdarzeń emituje zdarzenie newListener. To zdarzenie służy do wykrywania nowych programów obsługi zdarzeń. Zwykle używasz zdarzenia newListener, gdy musisz przydzielić zasoby lub wykonać jakąś akcję dla każdego nowego modułu obsługi zdarzenia.
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 Wyjaśnienie:-
- Tworzymy nowy moduł obsługi zdarzeń dla zdarzenia 'newListener'. Tak więc za każdym razem, gdy rejestrowany jest nowy moduł obsługi zdarzeń, w konsoli wyświetlany jest tekst „Added listener for” + nazwa zdarzenia.
- Tutaj zapisujemy do konsoli tekst „Dodano słuchacza dla” + nazwę zdarzenia dla każdego zarejestrowanego zdarzenia.
- Definiujemy 2 procedury obsługi zdarzeń dla naszego zdarzenia „data_received”.
Jeżeli powyższy kod zostanie wykonany poprawnie, w konsoli wyświetli się poniższy tekst. Pokazuje tylko, że procedura obsługi zdarzenia „newListener” została uruchomiona dwukrotnie.
Dodano detektor zdarzeń data_received
Dodano detektor zdarzeń data_received









