W jaki sposób node.js strumieniuje dane? I co to jest ten tick?! 🤔
Niedawno poruszyliśmy temat event loopa i operacji asynchronicznych I/O. Zostało powiedziane, że libuv wywołuje handler, gdy plik zostanie załadowany do pamięci. Co w przypadku strumieniowania? Czy stopniowe ładowanie plików do pamięci nie miało być głównym jego atutem? Jak to się ma do event loopa?
Korzystając ze streamów, definiujemy wielkość paczki (chunka), którą otrzymamy ze streama. Paczki reprezentują n-tą część np. pliku, który próbujemy odczytać. Są nam one dostarczane stopniowo, w postaci bufora. Buffer to obiekt w Node.js, który reprezentuje konkretną, stałą przestrzeń w pamięci. W momencie jego tworzenia z góry rezerwowana jest jego wielkość. Libuv, w przypadku streamowania, wywołuje kolejno Event Loop, dostarczając chunki pliku w postaci rzeczonego bufora.
Tworząc nowy stream, nasłuchujemy na zdarzenie .on(‘data’, …) i właśnie to zdarzenie jest uruchamiane przez libuv w momencie przygotowania kolejnego bufora z pliku.
Można się zastanawiać, czy takie odczytywanie pliku nie będzie zbyt powolne, gdy mielibyśmy czekać na każdy kolejny cykl, żeby otrzymać nową cząstkę naszego pliku? Na szczęście jedno nie zależy od drugiego. Libuv jest w stanie dostarczyć kilka paczek w ramach jednego ticka. A ten tajemniczy tick ⏰ to właśnie jeden pełen obieg pętli event loop.
Dzięki wbudowanym mechanizmom, możemy nasłuchiwać na kolejny rozpoczynający się tick i sprawdzić, czy jest dokładnie tak, jak mówimy:
import stream from "node:stream";const readable = new stream.Readable({ read() {} });readable.on("data", (chunk) =>console.log("załadowanie informacji: ", chunk.toString()));process.nextTick(() => console.log("kolejny cykl"));readable.push("pierwsza informacja w strumieniu");readable.push("druga informacja w strumieniu");setTimeout(() => {readable.push("trzecia informacja w strumieniu");});// Output:// załadowanie informacji: pierwsza informacja w strumieniu// załadowanie informacji: druga informacja w strumieniu// kolejny cykl// załadowanie informacji: trzecia informacja w strumieniu
Przy pierwszej iteracji event loopa zostały odczytane dwie wartości ze streamu. Dopiero opóźnienie wysłania przez setTimeout spowodowało, że stream zwrócił trzecią wartość w kolejnym cyklu.
Wykorzystana tutaj metoda nextTick jest również świetnym sposobem, by uruchomić jakąś akcję na początku kolejnego cyklu. Ale czy setTimeout nie robi tego samego? To trochę jak z !important w CSS-ach - nextTick jest jeszcze ważniejszy i jeszcze szybszy 😜. Jego callback uruchamiany jest przed operacjami Timerów i I/O. Jeśli potrzebujemy czegoś, co z pewnością wywoła się przed innymi operacjami w pętli - warto z niego skorzystać. Z drugiej strony mamy setImmediate, który wykona się zawsze, ale na końcu cyklu event loopa.