Asynchroniczność w JavaScript cz. 1

Cover Image for Asynchroniczność w JavaScript cz. 1
Paweł
Paweł

Przetwarzanie synchroniczne i asynchroniczne

Dziś lecimy z tematem, o którym spokojnie można napisać książkę. Mowa tutaj o temacie asynchroniczności w JavaScripcie.

Żeby nie przeciążyć was ilością informacji postanowiliśmy podzielić materiał i stworzyć krótką serię. Na początek wyjaśnijmy sobie, czym jest kod synchroniczny i asynchroniczny. Ułatwi to zrozumienie kolejnych artykułów. Następnie weźmiemy na tapet:

  • Callback
  • Promises i Async/await
  • Event Loop

Przetwarzanie Synchroniczne

Kod może być przetwarzany na dwa sposoby, synchroniczny i asynchroniczny.

Idea przetwarzania synchronicznego jest bardzo prosta, dlatego zaczniemy od niej. Każda instrukcja wykonywana jest krok po kroku. Spójrzmy na fragment kodu poniżej:

const first = 1; console.log(first); // 1 const second = 2; console.log(second); // 2

Jako wynik dostaniemy 1 i 2. Kod wykona się linijka po linijce.

A teraz wyobraźmy sobie sytuację, że pomiędzy tymi dwoma console logami będzie wykonywać się jakaś trwająca dłuższy czas operacja, na przykład jakś dużą pętlę jak w przykładzie poniżej:

const first = 1; console.log(first); for(var i = 0; i < 10000000000; i++) { } const second = 2; console.log(second);

Nasz program wyświetli zawartość zmiennej first, a następnie przystąpi do wykonywania wspomnianej wyżej pętli. W trakcie wykonywania pętli, środowisko uruchomieniowe nie będzie w stanie wziąć się za przetwarzanie innych operacji. Może to spowodować, że użytkownik będzie miał wrażenie "zamrożenia". Po przejściu ostatniej iteracji nasz program przejdzie do wyświetlenia wartości second.

Wyświetlenie wyniku wspomnianej zmiennej nie zależało od przejścia pętli. A gdyby tak blokująca operacja wykonywała się w tle, a w międzyczasie program mógł normalnie działać?

Przetwarzanie asynchroniczne

Tu właśnie wjeżdża przetwarzanie asynchroniczne.

Co nam to da?

Wróćmy do poprzedniego przykładu:

const first = 1; console.log(first); // start async for(var i = 0; i<10000000000; i++) { } // stop async const second = 2; console.log(second);

W miejscach oznaczonych start i stop zostanie użyty jeden z wbudowanych mechanizmów zapewnienia asynchroniczności w JS (na chwilę obecną nie chcemy wprowadzać kolejnych pojęć, więc zaufajcie nam na słowo).

W tym momencie rezultat, jaki zobaczymy na ekranie, będzie wyglądać tak:

1 2 // wynik asynchronicznego przetwarzania

Jak widać, uniknęliśmy oczekiwania na zakończenie długiej operacji i wyświetlimy od razu zawartość zmiennych first i second.

Tutaj musimy jeszcze wtrącić jedną dość istotną rzecz. Javascript jest z językiem jednowątkowym. Co to oznacza? Oznacza to, że silnik JS może wykonywać tylko jedną operację w tym samym czasie, a mechanizmy asynchroniczności wbudowane opierają się na Event Loop.

Przykład z życia

Żeby lepiej zobrazować, jak to wszystko działa posłużmy się pewną analogią. Pozwoli wam to lepiej zrozumieć powyższe procesy.

Wyobraźmy sobie, że jesteśmy w pizzerii, która ma jednego pracownika. Nazwijmy go Janek. Janek jest jednocześnie odpowiedzialny za kilka czynności: obsługę klienta, przygotowanie pizzy i wydanie jej dostawcy.

Cały proces pizzy w uproszczeniu wygląda następująco:

Process
Obsługa klienta/pobranie zamówienia -> rozgrzanie pieca -> przygotowanie pizzy -> pizza w trakcie pieczenia -> pizza gotowa do wydania dostawcy

Wyobraźmy sobie teraz, że powyższy flow jest całkowicie synchroniczny:

1. Janek zaczyna obsługę klienta, pobiera od niego informacje na temat zamówienia.
2. Zaczyna rozgrzewać piec.
3. Czeka, aż piec się nagrzeje do właściwej temperatury. W międzyczasie przychodzi kolejny klient.
4. Janek przystępuje do przygotowania pizzy.
5. Wsadza pizze do pieca i czeka, aż się upiecze. Pojawiają się kolejni klienci, kolejka rośnie.
6. W końcu pizza jest upieczona i może zostać wydana do doręczenia.

Taki sposób prowadzenia pizzeri, zdecydowanie nie sprawdziłby się w prawdziwym życiu.

A gdyby użyć przetwarzania asynchronicznego?

1. Janek zaczyna obsługę klienta.
2. Odpala piec.
3. W czasie gdy piec się nagrzewa, przychodzi kolejny klient.
4. Janek zajmuje się jego obsługą i zapisuje zamówienie na później. Wraca do przygotowania pizzy.
5. Piec sygnalizuje, że osiągnął odpowiednią temperaturę, pracownik ma już gotową pizzę i od razu może wsadzić ja do pieca. W czasie gdy pizza się piecze, Janek może obsługiwać kolejnych klientów.
6. Gdy pierwsza pizza jest już upieczona, może zostać wydana do doręczenia, a Janek może rozpocząć przygotowywanie kolejnego zamówienia.

Jak widać, takie przetwarzanie pozwala wykonywać cały proces bardziej efektywnie. Kolejni klienci nie muszą czekać tak długo, jak w pierwszym przypadku, Janek jest w stanie ich obsłużyć i pobrać zamówienie w trakcie, gdy np. piecze się pizza z poprzedniego zamówienia.

Podsumowanie

Czy w takim razie przetwarzanie asynchroniczne jest złotym środkiem na wszystko?

Niekoniecznie, oba podejścia mają wady i zalety, a najlepszy wybór będzie zależał od konkretnych wymagań Twojej aplikacji. Jako programiści musimy brać pod uwagę różne czynniki, przeanalizować co potencjalnie mogą pójść nie tak. Rozwiązanie powinno być dopasowane do problemu, a nie wybrane dlatego, że np. w danym momencie jest modne. Programowanie asynchroniczne jest z reguły bardziej złożone, ale w niektórych przypadkach może oferować lepszą wydajność. Programowanie synchroniczne jest prostsze do zrozumienia i implementacji, ale może prowadzić do spowolnienia działania aplikacji, jeśli będzie używane w niewłaściwy sposób.

Poniżej tabela porównawcza dla lepszego utrwalenia opisanej wiedzy:

Table

Podobne Tematy