Używanie natywnych modułów Node.js na przykładzie Path i Util

W tym artykule, który jest częścią kursu Node.js dalej kontynuujemy „podróż” po funkcjach i obiektach dostępnych w Node.js z poziomu globalnego.

A konkretnie zajmiemy się funkcją require().

Korzystaliśmy już z funkcji require() w poprzednim artykule z tego kursu do załadowania modułu Path i przypisania go do zmiennej o tej samej nazwie:

var path = require("path");

Funkcja require() służy do ładowania/importowania modułów Node.js. Moduł path jest dostępny w Node.js po jego zainstalowaniu na Twoim komputerze/serwerze (są to tak zwane moduły ‚core’). Znacznie większa kolekcja modułów znajduje się jednak na zdalnym repozytorium NPM. Aby z nich korzystać, trzeba je najpierw zainstalować, co zademonstruję w dalszej części kursu. Na razie omówimy sobie moduły, których użycie w aplikacji nie wymaga instalacji przez NPM.

Modułu path używa się do operacji związanych ze ścieżkami plików w aplikacji. Na przykład wcześniej użyliśmy jej do wyciągnięcia nazwy aktualnego pliku:

var path = require("path");
console.log(path.basename(__filename));

basename() to metoda w module path, która w połączeniu z parametrem __filename wyrzuca nazwę pliku (z którego została wywołana). Natomiast jeśli chcemy uzyskać nazwę aktualnego katalogu, możemy użyć parametru __dirname.

var path = require("path");
console.log(path.basename(__dirname));

Za pomocą modułu path, a konkretnie metody join(), możemy łatwo konfigurować swoje własne ścieżki i katalogi. Na przykład załóżmy, że chcemy utworzyć w naszej aplikacji katalog ‚uploads’ na pliki wgrywane przez użytkowników, a w nim utworzyć podkatalog ‚images’ na zdjęcia. Na koniec chcemy otrzymać pełną ścieżkę dostępu do tego katalogu. Dzięki metodzie join() modułu path jest to banalne:

var path = require("path");
console.log(path.join(__dirname, 'uploads','images'));

Czyli do aktualnego katalogu dodajemy dwa foldery, funkcja console.log() wyświetla całą ścieżkę na ekranie.

tutorial-node-js-moduly

Innym interesującym modułem dostępnym natywnie w Node.js jest moduł util. Zaimportujmy go sobie do zmiennej util:

var util = require("util");

Aha, nie musimy trzymać importowanych modułów w zmiennych var, mogą to być również stale const, jeśli mamy pewność, że nie będziemy ich zmieniać. A w przypadku importowania modułów w Node.js, najczęściej tak właśnie jest.

Więc dla odmiany, zainicjujmy ten moduł do stałej util:

const util = require("util");

Moduł ten posiada sporo funkcji-helperów ułatwiających proces tworzenia aplikacji, a szczególnie własnych modułów. Ich pełna lista znajduje się oczywiście w dokumentacji Node.js.

Jedną z interesujących metod jest log(). Możemy jej użyć zamiast javascriptowej standardowej console.log(). Różnica jest taka, że oprócz logowania informacji na ekran, util.log() dodaje również timestamp, kiedy zostało to wykonane. Zastąpmy console.log() w naszym poprzednim skrycie metodą util.log():

var path = require("path");
const util = require("util");
util.log(path.join(__dirname, 'uploads','images'));

Po uruchomieniu mamy taki wynik:

tutorial-node-js-dodawanie-modulow

Widzimy, że oprócz ścieżki dostępu mamy czas wykonania 31 lipca o 09:56.

Jako, że Node.js jest ‚zasilany’ preprocesorem v8 z przeglądarki Google Chrome, możemy użyć tego modułu i jego metod do np. sprawdzenia aktualnego zużycia pamięci serwera. Taką metodą jest getHeapStatistics(). Zobaczmy jak działa, dodając ją do naszego poprzedniego skryptu:

var path = require("path");
const util = require("util");
const v8 = require("v8"); // importujemy moduł v8 do stałej v8
util.log(path.join(__dirname, 'uploads','images'));
util.log(v8.getHeapStatistics()); // wyświetlamy informacje o aktualnym zużyciu pamięci

Uruchamiamy i widzimy dodatkowy log – obiekt zawierający różne parametry dotyczące aktualnych zasobów serwera:

tutorial-node-js-modul-v8

Asynchroniczność w Node.js – pierwsze kroki

W tym tutorialu zapoznamy się z asynchronicznością Node.js na przykładzie wykorzystania funkcji związanych z kontrolą upływu czasu, które dostępne są globalnie w Node.js.

Tworzymy plik apka.js i umieszczamy w niej poniższy kod, który najpierw inicjuje zmienną wait z wartością 3000, wyrzuca do konsoli tekst Zaczynamy... a następnie uruchamia funkcję setTimeout(), która z kolei wykonuje swój pierwszy parametr (czyli funkcję callback) najszybciej po upływie czasu określonego w drugim parametrze, czyli wait. Jak głosi dokumentacja, parametr ten podać należy w milisekundach, czyli tysięcznych częściach sekundy. W naszym przypadku będą to 3000 milisekund = 3 sekundy. Funkcja, która wykona się po upływie tych 3 sekund nie robi nic innego, tylko wyrzuca do konsoli tekst 'Upłynęły minimum 3 sek...'.

var wait = 3000;
console.log("Zaczynamy...");
setTimeout(function(){
    console.log("Upłynęły minimum 3 sek...")
}, wait);

Po zapisaniu i uruchomieniu skryptu komendą node w wierszu poleceń, powinniśmy zobaczyć taki wynik:

kurs-node-js-poczatki

Przy czym ten drugi tekst pojawi się oczywiście nie od razu, ale po upływie co najmniej 3 sekund.

Zobaczmy, co się stanie, gdy dodamy jeszcze jeden console.lo() na koniec kodu:

var wait = 3000;
console.log("Zaczynamy...");
setTimeout(function(){
    console.log("Upłynęły minimum 3 sek...")
}, wait);
console.log("jestem na końcu ;)");

Wynik:

kurs-node-js-pierwsze-kroki-asynchroniczny

Fakt, że instrukcja, która w naszym kodzie jest ostatnia wcale nie wykonała się jako ostatnia ale przed callbackiem z funkcji setTimout() świadczy o asynchronicznym działaniu javascriptu i całego Node.js. Nasz program, w momencie gdy dotarł do instrukcji setTimeout() nie czekał aż miną 3 sekundy, ale zlecił tej funkcji odczekanie tych 3 sekund, wykonanie callbacka i wrócenie z wynikiem. Sam natomiast wykonał kolejną instrukcję, czyli console.log(„jestem na końcu ;)”);. Kiedy setTimeout() wrócił do głównego wątku naszego programu z wynikiem, wtedy został wykonany. Stąd taka kolejność komunikatów naszego programu. Więcej o asynchroniczności napisałem w tym artykule: Co to znaczy, że Javascript jest asynchroniczny?

Pierwsza aplikacja w Node.js

Zanim napiszemy pierwszy skrypt w Nodejs, troszeczkę teorii.

Aplikacje pisane w Node.js posiadają modułową architekturę. Każdy plik .js stanowi swój odrębny moduł, a zmienne, które w nim deklarujemy posiadają scope w obrębie tego modułu. W odróżnieniu od czystego javascriptu w przeglądarkach, gdzie

var zmienna = 5

znajduje w scopie globalnym i może wywołana przez window.zmienna. W Node.js tak nie zrobimy.

Ale Node.js również posiada obiekty globalne, a zdefiniowane w nich metody dostępne są dla programistów w każdym miejscu tworzonej aplikacji. Ich lista znajduje się w oficjalnej dokumentacji https://nodejs.org/api/globals.html. Jednym z obiektów globalnych według tej dokumentacji jest console. Oznacza to, że możemy sobie z tego obiektu i jego metod korzystać od razu, bez importowania dodatkowych modułów. Sprawdźmy więc, czy tak jest w istocie i przy okazji napiszmy naszą pierwszą aplikację w Node.js 😉 Aplikacja będzie korzystać – jakżeby inaczej – z popularnej metody console.log().

W dowolnym miejscu na swoim komputerze stwórz pusty plik apka.js. Ja zrobię to tutaj: c:\node-apps\apka.js. Następnie umieść w nim ten kod:

var zmienna = 'Czy Node jest fajny? Zobaczymy!';
console.log(zmienna);

Oto i nasza pierwsza aplikacja. Zapisujemy plik apka.js. Otwieramy wiersz poleceń i uruchamiamy aplikację komendą node apka.js

tutorial-node-js-jak-pisac

(oczywiście pamiętamy aby komendę wykonać będąc w tym samym katalogu wiersza poleceń, w którym jest plik apka.js)

Powinniśmy otrzymać taki wynik:

tutorial-node-js-pierwsza-aplikacja

Jak widzimy, dokumentacja Node.js nie kłamie, możemy korzystać z obiektów globalnych do woli. Przy okazji stworzyliśmy poprawnie działającą pierwsza aplikację w Node.js 😉

Dokumentacja głosi także, że oprócz tych obiektów globalnych unikalnych dla Node.js, możemy dodatkowo korzystać globalnie z pokaźnej listy obiektów (a zatem też ich metod) wbudowanych globalnie w ‚rdzeń’ javascriptu, np. Number, Math, Date, Array. Ich wykaz znajduje się pod tym linkiem: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects. Przekonajmy się, czy tak rzeczywiście jest. Skoro możemy korzystać z funkcji przypisanych do obiektu String, to slice() powinno zadziałać. Robimy zmiany w kodzie naszej apki, aby uciąć tekst z oryginalnego zdania do 20-ej litery i wyświetlić to, co zostało:

var zmienna = 'Czy Node jest fajny? Zobaczymy!';
var odp = zmienna.slice(21);
console.log(odp);

Rzeczywiście tak jest, aplikacja wyświetliła poprawny wynik:

nodejs-dla-poczatkujacych

Przy wyświetlaniu stringów w Node.js możemy też zastosować tzw. interpolację i połączyć naszą zmienną z jakimś innym tekstem. Używamy wtedy grawisów, czyli czegoś takiego: `. Jest to znak umieszczony najczęściej na klawiaturze przed klawiszem z cyfrą 1 i nad klawiszem TAB (pierwszy w rzędzie z klawiszami numerycznymi.). Następnie przed nazwą zmiennej umieszczamy znak dolara i zamykamy ją w klamerkach {} :

console.log(`Czy Node.js jest fajny? ${odp}`);

Nasza aplikacja zwróci wtedy taki wynik:

tutorial-node-js-interpolacja

Funkcja require()

Nasza aplikacja potwierdziła, że w Node.js możemy korzystać z wielu globalnych obiektów i metod. Jedną z ważniejszych funkcji dostępnych globalnie jest require(). Używa się jej do importowania modułów Node.js. Zobaczmy jak działa. Na początku pliku apka.js dodajemy linijkę:

var path = require("path")

Path to moduł dostępny natywnie w Node.js od razu po jego zainstalowaniu i zgodnie ze swoją nazwą – zajmuje się zarządzaniem ‚ścieżkami’. W powyższej linii ‚zaciągneliśmy’ go sobie do zmiennej o tej samej nazwie – path.

Następnie kasujemy w naszej aplikacji kolejne dwie linie:

var zmienna = ‚Czy Node jest fajny? Zobaczymy!’; var odp = zmienna.slice(21);

Zostawiamy tylko ostatnią:

console.log(`Czy Node.js jest fajny? ${odp}`);

Ale zmieniamy ją, aby miała taką treść:

console.log(`Witamy z aplikacji Node.js, z pliku ${path.basename(__filename)}`);

Jak widzisz korzystamy tu z metody modułu path o nazwie basename, która zwraca pełną nazwę ścieżki do aktualnego pliku. Natomiast w połączeniu z parametrem __filename zwraca tylko nazwę aktualnego pliku. Po powyższych zmianach, kod całej aplikacji to tylko dwa wiersze:

var path = require("path")
console.log(`Witamy z aplikacji Node.js, z pliku ${path.basename(__filename)}`);

Uruchamiamy aplikację. Jak już wiesz, robimy to komendą node apka.js jednak możemy pominąć rozszerzenie pliku .js i wpisać tylko node apka. Komenda node uruchamia pliki .js domyślnie, dlatego można rozszerzenie pominąć. Przy okazji – kolejną rzeczą, którą możemy pominąć to średniki na końcu linijek w kodzie – tak samo jak w javascript, nie ma to wpływu na poprawność kodu. To, czy je stosujesz, zależy od Twoich preferencji.

Po uruchomieniu aplikacji powinniśmy mieć wynik, który jest nazwą pliku z kodem źródłowym naszej aplikacji:

tutorial-node-js-jak-uruchomic-aplikacje

To wszystko, co chciałem przedstawić w tej lekcji. Zachęcam do lektury powyższych linków do dokumentacji Node.js i zabawy z innymi funkcjami dostępnymi globalnie. I oczywiście do kolejnej części kursu.

Jak zainstalować Node.js

Najłatwiejszym sposobem zainstalowania Node.js jest odwiedzenie oficjalnej strony https://nodejs.org/en/ i pobranie go poprzez kliknięcie z zielony przycisk na głównej stronie. Spowoduje to pobranie Node.js w wersji na Twój system. W momencie gdy dodaję ten tutorial, dostępne są dwa przyciski:

tutorial-node-js-jak-zainstalowac

Node.js jest często aktualizowany, więc prawdopodobnie widzisz już inne cyferki przy numerze wersji. Tak czy inaczej proponuję wybrać przycisk z lewej, ponieważ prowadzi on do ostatniej stabilnej wersji, natomiast przycisk po prawej jest najnowszym, ale może nie do końca przetestowanym w naturalnym i praktycznym środowisku wydaniem.

Możesz także zajrzeć do zakładki Downloads na powyższej stronie i stamtąd wybrać wersję instalatora oraz wersję systemową.

Po pobraniu instalatora, odpalamy go i postępujemy według instrukcji. Instalacja w domyślnych opcjach jest jak najbardziej zalecana, więc nie musimy nic zmieniać czy dodatkowo konfigurować. Jeśli jednak życzysz sobie zmienić ścieżkę instalacji (na Windowsie domyślnie będzie w Program Files), śmiało.

Po zainstalowaniu sprawdźmy, czy wszystko przebiegło pomyślnie. Wchodzimy w wiersz poleceń lub terminal, w zależności od systemu operacyjnego na naszym komputerze. Wiersz poleceń na Windows uruchamiamy przez kliknięcie na menu Start i wpisanie w wyszukiwarkę hasła: cmd.

W wierszu poleceń wpisujemy komendę:

node -v

i wciskamy Enter. W odpowiedzi uzyskamy wersję Node, którą właśnie zainstalowaliśmy. W moim przypadku jest to wersja 6.10.2

tutorial-node-js-instalacja-na-ubuntu-windows

Gratuluję, od teraz możesz programować w Node.js na swoim komputerze :).

Jak działa Node.js

Node.js to narzędzie, które umożliwia tworzenie i utrzymywanie skryptów w języku Javascript poza środowiskiem przeglądarki internetowej. Oznacza to, że możemy uruchamiać aplikacje javascript na komputerze, laptopie, czy serwerze i nie musimy do tego celu włączać przeglądarki. Ten szybki framework jest dostępny na wszystkich głównych środowiskach operacyjnych – Windowsie, Linuksie i MacOS.

Niewątpliwie jasnym punktem Node.js jest również npm, czyli Node Package Manager – narzędzie działające w command line, które daje programistom dostęp do potężnej biblioteki darmowych (open-source) modułów i bibliotek ułatwiających i przyspieszających tworzenie aplikacji.

Mówi się, że Node.js działa w systemie non-blocking w odróznieniu od np. zestawu PHP+Apache, który działa w systemie blocking. Innymi słowy: asynchronicznie i synchronicznie.

Pozwolę sobie wyjaśnić różnicę na przykładzie wizyty na poczcie w celu odebrania paczki, w której czynne jest jedno okienko. node.js-tutorial-jak-dziala

Scenariusz 1, poczta działa w Apache + PHP

Taką właśnie pocztę, (którą każdy z nas zna i czasem – chętnie lub niechętnie – odwiedza) można porównać do serwera Apache z PHP. Podchodzimy do okienka, podajemy pani awizo. Jest to nasz request do serwera. Pani zabiera awizo i idzie na zaplecze w poszukiwaniu naszej paczki. Przez ten czas my stoimy przy okienku i cierpliwe czekamy, aż nasz request do serwera przyniesie wynik, czyli aż nasza pani z poczty przyniesie paczkę z zaplecza.

Poczta działa synchronicznie. Do momentu aż pani z okienka do nas wróci, działanie poczty jest zablokowane. Nie możemy wydać innego polecenia (np. kupić znaczek albo pocztówkę), pozostali klienci również czekają. Nie możemy zrobić nic innego, serwer wykonuje nasz request i wykona kolejny dopiero wtedy, gdy skończy aktualny.

Scenariusz 2, poczta działa w Node.js

Natomiast gdyby poczta była stworzona w Node.js, nasza wizyta przebiegłaby mniej więcej tak: Wchodzimy na pocztę, podchodzi do nas pani obsługująca klientów. Wydajemy jej prośbę (przekazujemy awizo). Pani zabiera awiso, ale wcale nie idzie na zaplecze szukać naszej paczki, ale przekazuje je asystentowi – magazynierowi, który przejmuje to zadanie. Natomiast pani na poczcie gotowa jest w tym momencie obsłużyć kolejną prośbę – którą np. jest kupno znaczka. I analogicznie, pani bierze od nas pieniądze na znaczek i przekazuje je kasjerowi, który teraz przejmie od niej zadanie odszukania znaczka w asortymencice, zaksięgowania wpłaty w kasie, przyszykowania reszty itp. Pani obsługująca klientów może w tym czasie obsłużyć kolejną prośbę.

Jak zauważyłeś, pani na poczcie w tym scenariuszu jest cholernie obrotna. Celowo nie piszę już ‚pani z okienka’, bo pani nie ma czasu siedzieć w okienku. Biega od klienta do klienta, spełniając ich żądania.

Ale co z naszą paczką? Kiedy magazynier odnajdzie ją na zapleczu, przynosi ją do pani obsługującej klientów, a ona przekazuje ją nam. To obrazuje, dlaczego Node.js jest asynchroniczny. Poczta równolegle szukała naszej paczki (za sprawą magazyniera), a w międzyczasie obsługiwała inne prośby klientów.

Mam nadzieję, że to porównanie do poczty jest trafione i pomocne :). Jeszcze kilka słów podsumowania.

W założeniu, asynchroniczna poczta Node.js działa po prostu sprawniej niż synchroniczna poczta Apache. W powyższym porównaniu pani z okienka jest wątkiem serwera. Zatem zarówno Node.js jak i Apache (w domyśle, chociaż można go zmusić do innego trybu) działają w jednym wątku. Z tą róznicą, że Node.js działa asynchronicznie, a Apache synchronicznie. Chociaż dla wnikliwych, w przypadku Node.js mamy do czynienia z dwoma wątkami. W tym drugim, pobocznym wątku wykonują się działania magazyniera czy kasjerki przy kupowaniu znaczka, czyli instrukcje mające dziać się asynchronicznie, równolegle – tak aby główny wątek mógł w tym czasie nieprzerwanie robić coś innego. Generalne instrukcje wykonywane są przez jeden wątek, natomiast instrukcje asynchroniczne „przejmowane” są przez drugi wątek, który zwraca ich wynik do głównego wątku (paczka, znaczek). Wyniki układane są w kolejkę do tego głównego wątku (kto pierwszy ten lepszy), który zbiera je po kolei. Trochę więcej na ten temat znajdziesz w tym osobnym artykule na temat asynchronicznego Javascriptu.

Kurs Node.js – poznać i zrozumieć

Witaj w kursie Node.js, którego celem jest otwarcie Ci drzwi do tego środowiska programistycznego. Kurs składa się z szeregu tutoriali, w których opisuję jak postawić pierwsze kroki w Node.js, poznać jego najważniejsze funkcje i nauczyć się z nich poprawnie korzystać.

Każdy tutorial Node.js, który znajdziesz poniżej składa się na cały kurs. Jeśli zapoznasz się z nimi, powinieneś wiedzieć jak działa Node.js w swojej najważniejszej, asynchronicznej postaci, czym jest event loop, obiekty globalne, jak asynchronicznie zarządzać plikami (otwarcie, edycja, odczytywanie), komunikacja z innym serwerem poprzez web sockety i zapytania http. Dowiesz się również jak pracować z npm, czyli node package manager.

Do kogo skierowany jest ten kurs?

Sam dopiero poznaję tajniki Node.js, więc z pewnością nie zamierzam w tym miejscu pouczać wyjadaczy tego frameworka. Poniższe tutoriale skierowane są raczej do osób zaciekiowionych Node.js, którzy chcieliby sprawdzić ‚z czym to się je’. Jednakowoż dobrze, jeśli masz za sobą trochę praktyki w czystym Javascript, ponieważ programowanie w Node.js to właściwie programowanie w Javascript.

Zaawansowanych programistów również zapraszam do lektury i odświeżenia sobie podstaw. A także do komentarzy i zgłaszania usprawnień, lepszych praktyk lub własnych pomysłów :).

Kurs jest na bieżąco aktualizowany o nowe tutoriale, w miarę jak powstają.