co to znaczy asynchroniczny javascript

Co to znaczy że Javascript jest asynchroniczny?

Stawiasz pierwsze kroki w Javascripcie? A może język ten towarzyszy Ci już od dawna, jednak dopiero od niedawna zaczynasz poznawać jego mechanikę, próbując zrozumieć logikę stojącą za wykonanym kodem? Ja, będąc samoukiem o bardziej backendowym doświadczeniu, należę do tej drugiej grupy. Przeglądając różne kursy i tutoriale, trafiałem raz po raz na to magiczne słowo 'asynchroniczność’ – które w wielu przypadkach stanowiło wytłumaczenie dla jakiejś kwestii językowej, natomiast dla mnie było niezrozumiałe. Jeśli też szukasz informacji na ten temat, poniżej postaram się wytłumaczyć, dlaczego mówi się, że kod javascript jest asynchroniczny.

Zacznijmy jednak z innej strony, czyli co oznacza synchroniczny kod?

Javascript tak naprawdę nie jest w 100% asynchroniczny. Znaczna jego część działa również synchronicznie. Kod wykonywany synchronicznie to taki, który wykonuje się linijka po linijce w kolejności, jakiej został napisany. Oczywiście mam tu na myśli niekoniecznie cały plik ze skryptem ale także krótszy blok kodu, stanowiący swoją odrębną całość – np. funkcja, która potem może być wywoływana w innym miejscu. Przykład synchronicznego javascriptu mamy poniżej:

var age = 24;
console.log(age);
age = age + 1;
console.log(age);

Powyższy 4-wierszowy kod wykonuje się wiersz po wierszu, czyli jeśli skopiujesz ten kod i wkleisz do konsoli przeglądarki (śmiało, możesz zrobić to nawet teraz jak to czytasz), otrzymasz wynik:

24
25

Oto jak przeglądarka interpretuje powyższy kod. Najpierw zaglądamy do pierwszej linijki, gdzie nastąpiła deklaracja (a zarazem również inicjalizacja) zmiennej age oraz przypisanie jej wartości 24. To wszystko w pierwszym wierszu. Następnie przeglądarka przeszła do drugiej linijki, aby ją zinterpretować i wykonała znajdujący się w niej kod, 'wypluwając’ do konsoli wartość zmiennej age za pomocą funkcji console.log(). Czyli 24. Następnie zwiększyliśmy wartość age o 1 (trzecia linijka), a w czwartej wypisaliśmy nową wartość (25) w konsoli – znów za pomocą funkcji console.log().

A teraz zobacz co się dzieje, gdy przed ostatnią linijkę wstawimy do wykonania inną funkcję (która działa właśnie asynchronicznie):

var age = 24;
console.log(age);
age = age + 1;
// start nowy fragment
$.get('plik.txt', function () { 
    console.log('Mam plik.txt, więc teraz wykonam asynchronczne instrukcje stąd.');
});
// end nowy fragment
console.log(age);

W rezultacie otrzymamy taki wynik:

24
25
Mam plik.txt, więc teraz wykonuję asynchronczne instrukcje stąd.

Rzut okiem na rezultat i już widzimy, gdzie tkwi różnica między kodem synchronicznym a asynchronicznym. Mimo, że dodaliśmy fragment wywołujący funkcję $.get przed kodem wyświetlającym wartość age = 25. to w wynikach mamy ewidentnie dowód na to, że skrypt tej funkcji (czyli console.log(Mam plik.txt, więc teraz wykonuję asynchroniczne instrukcje stąd.)) wykonał się później.

To dlatego, że $.get to funkcja asynchroniczna. Przeglądarka doszła do linijki, w której ją wywołujemy i synchronicznie wykonała tylko pierwszą jej część – oderwała się od głównego wątku w poszukiwaniu pliku plik.txt. Ale główny wątek nie czeka, aż plik zostanie odnaleziony (lub czy w ogóle) tylko bierze się za interpretację kolejnej linii (console.log(age)) i wyrzuca ją na ekran konsoli. Dopiero później do wątku wracają informacje o odnalezieniu pliku plik.txt oraz instrukcje, co należy w tym przypadku wykonać (jeśli plik nie zostałby znaleziony, możemy tu wyświetlić błąd). Zapisane są one w drugim parametrze tej funkcji, która notabene też jest funkcją. Jest to tzw. callback (call back – czyli „wykonaj po powrocie”), który wykonuje jedną operację po odnalezieniu pliku: console.log('Mam plik.txt, więc...');.

Plusy i minusy asynchronicznego Javascriptu

W powyższym przykładzie, dzięki temu że Javascript pozwala na asynchroniczne wykonywanie kodu, możemy cieszyć się niezaburzonym działaniem naszej aplikacji w razie gdyby 'zgarnięcie’ pliku z serwera okazało się dłuższe, niż cierpliwość użytkownika:) Zamiast bezczynnie czekać na zwrot zawartości pliku, skrypt może w tym czasie wykonać inne rzeczy.

Z drugiej strony asynchroniczny kod sprawia, że dużym wyzwaniem staje się utrzymanie pełnej kontroli nad tym, kiedy i w jakiej kolejności wykonają się asynchroniczne fragmenty. Gdybyśmy do naszego powyższego kodu dodali jeszcze jedno wywołanie $.get…:

var age = 24;
console.log(age);
age = age + 1;
$.get('plik.txt', function () { 
    console.log('Mam plik.txt, więc teraz wykonam asynchronczne instrukcje stąd.');
});
// start: Kolejne wywołanie $.get
$.get('plik2.dat', function () { 
    console.log('Mam plik2.dat, więc teraz wykonam asynchronczne instrukcje stąd.');
});
// end
console.log(age);

… zaczęłyby się schody, ponieważ tak naprawdę nie wiemy, który z plików zostanie zgarnięty wcześniej i związana z nim funkcja callback wykona się pierwsza. Wpływ na to ma nie tylko rozmiar plików (który jest większy) ale też przepustowość łącza, obciążenie serwera w danym momencie itd. Nowoczesny javascript pełen jest programowania asynchronicznego, dlatego jeśli chcemy się w nim odnaleźć i być w nim dobrzy, musimy nauczyć się tworzyć kod, który będzie działać stabilnie niezależnie od tego, który asynchroniczny callback wykona się pierwszy. Co oczywiście w pewnych okolicznościach może być trudne.