Asynchroniczność w JavaScript dla początkujących

Tematem dzisiejszego posta jest asynchroniczność w JavaScript. Brzmi jak coś bardzo skomplikowanego, prawda? Jak zwykle, to tylko pozory. Tak naprawdę, asynchroniczność to prosta ale ważna idea.

Każdy kto pisał kod dla front-endu, pewnie spotkał się już z wywołaniami asynchronicznymi. Pobieranie plików z serwera AJAXem odbywa się właśnie w taki sposób. Korzystanie z JS’a po stronie serwera, często również wymaga sporo wywołań asynchronicznych. Ale na czym to w ogóle polega?

Asynchroniczność w JavaScript dla początkujących

Zacznę od anegdoty. Całkiem niedawno na rozmowie kwalifikacyjnej (stanowisko JS developer) zadano mi pytanie: „czy JavaScript jest językiem jedno czy wielowątkowym”. Zgodnie z prawdą powiedziałem, że JS to język jednowątkowy. Chcąc zabłysnąć dodałem też, że można jednak zasymulować wielowątkowość. Czytałem o tym chociaż nie do końca potrafiłem wyjaśnić o co chodzi. Trochę się wtedy wpakowałem się, (nie róbcie tak nigdy 🙂 ), ale ostatecznie udało mi się z tego wybrnąć.

Dlaczego o tym piszę? Bo to co potrzeba do zasymulowania (w pewnym stopniu) wielowątkowości to właśnie wywołania asynchronicznie.

Co to jest język programowania jednowątkowy? W skrócie, program napisany w takim języku może robić tylko jedną rzecz na raz. Operacje wykonywane są po kolei, tak jak zostały zakodowane. W sumie to dobrze, prawda? W końcu nie było by zbyt fajnie, gdyby fragmenty kodu odpalały w losowej kolejności… I tak, i nie 🙂

Czas na przykład. Oto, krótki ‚program’ napisany w nodzie:

Moduł fs pozwala na przeprowadzanie operacji na plikach. Używam jego metody readFileSync, zwracającej zawartość pliku, do którego ścieżka przekazana jest w pierwszym argumencie. Drugi argument to sposób kodowania pliku.

Jak zadziała ten program? Dokładnie tak jak to wygląda na pierwszy rzut oka. Wczyta się pierwszy plik, w konsoli pojawi się jego zawartość, następnie wczyta się drugi plik, konsola wyświetli i jego zawartość a na koniec to samo stanie się z trzecim plikiem. Oto wynik działania tego kodu:

async JS

I to jest właśnie jednowątkowość. W jednej chwili może dziać się tylko jedna rzecz. Wszystko działa synchronicznie, po kolei. Problem w tym, że jeżeli plik będzie bardzo duży, program będzie wstrzymany dopóki nie skończy się jego ładowanie.

Rzecz w tym, że metoda readFileSync jest dość specyficzna. W JS wczytywanie plików jest zazwyczaj asynchroniczne. Co to oznacza? Oznacza to, że pomimo tego iż to język jednowątkowy, pliki wczytywane są równolegle do innych wykonań.

Czas spojrzeć na kolejny przykład. Tym razem użyje metody readFile modułu fs, która wczytuje pliki asynchronicznie:

Tu struktura kodu wygląda inaczej. Metoda readFile przyjmuje jeden dodatkowy argument – funkcję. Funkcja ta to tak zwane wywołanie zwrotne (callback) i zostanie ona wywołana dopiero kiedy plik zakończy się ładować.

Funkcja zwrotna przyjmuje dwa argumenty. Pierwszy z nich to obiekt błędu, jeżeli taki nastąpi podczas ładowania pliku. Drugi argument to zawartość pliku. Te informacje przekazywana są funkcji przez system.

Tym razem drugi wczytywany plik to zdjęcie. Ma ono dużo większy rozmiar niż pliki tekstowe.

A oto wynik działania tego kodu:

async JS

Jak widać, informacja o wczytaniu pliku zdjęcia pojawiła się na końcu, pomimo tego, że w kodzie jest na drugim miejscu. Dlaczego tak jest? Czyżby nagle JS stał się wielowątkowym językiem? Nie do końca, prawda jest taka, że program wykonał się po kolei, po prostu plik zdjęcia ładował się najdłużej.

W uproszczeniu, powstała kolejka ładowanych plików, za każdym razem, gdy któryś skończył się ładować, została wywołana przypisana do niego funkcja zwrotna.

Tak właśnie działa asynchroniczność. Plik jest dodawany do kolejki ładowania i program jest uruchamiany dalej.

Zmienię trochę poprzedni program aby ostatecznie pokazać co się dzieje:

Na końcu kodu dodałem jeszcze jeden wpis do konsoli. Wyświetli on tekst ‚koniec programu!’.

A oto wynik działania tego kodu:

async JS3

„Koniec programu”, pojawiło się na początku. Przeanalizuję krok po kroku co tu się wydarzyło. Po uruchomieniu programu, do kolejki wczytywania plików dodany został plik pies.txt. Następnie do kolejki trafia zdjęcie pies.jpg. W końcu do kolejki wrzucany jest ostatni plik pies3.txt. Gdy to jest gotowe, wywoływany jest ostatni console.log, czyli pierwsza rzecz, która pokazuje się na ekranie. Kolejny krok, to skończenie ładowania się pierwszego pliku, informuje o tym odpowiednia funkcja zwrotna. Następnie ładować kończy się trzeci plik, a na końcu zdjęcie które jest największe.

Po co to wszystko? Odpowiedź jest prosta. Nikt nie chce aby jego program zatrzymywał się za każdym razem gdy trafi na jakiś większy plik (a w szczególności, nie chce tego użytkownik programu).

Zagadnienie asynchroniczności jest bardzo ważne w programowaniu w JavaScripcie. Nie są to skomplikowane rzeczy, ale chodzi o to aby wiedzieć jakie mogą być tu następstwa. Szczególnie, że programista tak naprawdę nie jest w stanie przewidzieć, który plik wczyta się pierwszy. Na szczęście do dyspozycji są wywołania zwrotne (callback) i tam najczęściej dzieje się prawdziwa magia 🙂 Ale o tym napiszę w kolejnym poście.

Mam nadzieję, że ten post wyjaśnił komuś o co chodzi z tą całą asynchronicznością. Zachęcam do polubienia mojej strony na facebooku. Zamieszczam tam informacje o wszystkich nowościach. Polubienie strony gwarantuje bycie na bieżąco z postami 🙂

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *