Miniatura artykułu

Event Loop w JS – jak działa serce asynchroniczności?

5+ minut

Skopiuj link

Data publikacji: 13.10.2025 12:33

Ostatnia aktualizacja: 13.10.2025

Wprowadzenie

Jeśli kiedykolwiek zastanawiałeś się, dlaczego niektóre instrukcje w JavaScript wykonują się szybciej od innych, albo czemu setTimeout(fn, 0) wcale nie oznacza ‘wykonaj natychmiast’, ten artykuł jest dla Ciebie. Poznasz krok po kroku, jak działa jeden z kluczowych mechanizmów języka – pętla zdarzeń (event loop). To wiedza, która pozwoli Ci pisać bardziej świadomy i responsywny kod.

JavaScript to język jednowątkowy, a mimo to pozwala na obsługę wielu zadań pozornie jednocześnie: od pobierania danych z API, przez animacje, aż po reagowanie na kliknięcia użytkownika. To właśnie event loop sprawia, że Twój kod może pozostać responsywny, nawet gdy dzieje się w nim sporo rzeczy naraz.

JavaScript posiada mechanizm do obsługi zadań przychodzących, a pętla zdarzeń jest kluczową częścią tego mechanizmu. W jego skład wchodzą także call stack, task queue oraz micro task queue, ale wszystko po kolei...

Synchroniczność vs Asynchroniczność

Zanim przejdziemy do samej pętli zdarzeń, musisz dobrze zrozumieć fundament, na którym wszystko się opiera: JavaScript działa w jednym wątku. Oznacza to, że w danym momencie może wykonywać tylko jedną instrukcję. Każda funkcja trafia na stos wywołań, a kolejne fragmenty kodu są wykonywane dopiero po zakończeniu poprzednich.

Ta jednowątkowość niesie ze sobą dwie ważne konsekwencje:

Blokowanie kodu
Jeśli napiszesz funkcję, która wykonuje się bardzo długo (np. intensywne operacje numeryczne czy przetwarzanie dużych zbiorów danych), to w tym czasie żaden inny fragment Twojego skryptu nie zostanie uruchomiony. Strona może wyglądać na „zamrożoną”, bo JavaScript nie ma jak równolegle obsłużyć kliknięcia użytkownika czy odpowiedzi z serwera.

Potrzeba asynchroniczności
Gdyby JavaScript wykonywał wszystko wyłącznie synchronicznie, Twoje aplikacje byłyby powolne i mało responsywne. Właśnie dlatego przeglądarka udostępnia zewnętrzne API – mechanizmy, które wykonują cięższą pracę „poza” głównym wątkiem. Dzięki nim możesz np. ustawić opóźnienie (setTimeout), wysłać zapytanie sieciowe (fetch) czy zarejestrować reakcję na kliknięcie (addEventListener)

Spójrz na poniższe przykłady:

To, że JavaScript jest jednowątkowy, wcale nie oznacza więc, że musi działać powoli. Kluczowe jest to, w jaki sposób wyniki zadań asynchronicznych wracają do głównego wątku w odpowiednim momencie – i tutaj właśnie pojawia się event loop, o którym opowiem w następnych częściach.

Callstack

To tutaj JavaScript wykonuje kod synchroniczny. Stos wywołań przechowuje aktywne funkcje w kolejności ich uruchomienia. Każda nowa funkcja trafia na szczyt stosu, a po zakończeniu wykonywania jest z niego zdejmowana. Jest tutaj zastosowana metoda LIFO (Last In, First Out), która oznacza, że pierwsze wychodzi to, co przyszło jako ostatnie. JavaScript nie może wykonać żadnego innego zadania, dopóki stos nie zostanie całkowicie opróżniony – dlatego długie, blokujące operacje potrafią „zamrozić” stronę.

Przygotowałem prostą animację, abyś mógł lepiej zrozumieć, o czym mówię:

Wywołanie funkcji typeSomething() trafia na stos, a po wykonaniu zostaje z niego usunięte.

Wywołanie funkcji typeSomething() trafia na stos, a po wykonaniu zostaje z niego usunięte.

Aby lepiej pokazać, jak działa metoda LIFO, przygotowałem kolejną animację, na której widać, gdzie wykonanie jednej funkcji zostaje zagnieżdżone w innej.

Podczas wykonywania typeSomething() na stos trafia także console.log(), który po zakończeniu jest zdejmowany jako pierwszy.

Podczas wykonywania typeSomething() na stos trafia także console.log(), który po zakończeniu jest zdejmowany jako pierwszy.

API przeglądarki

W poprzedniej części pisałem, że JavaScript działa w jednym wątku i potrzebuje wsparcia, aby radzić sobie z wieloma zadaniami naraz. To wsparcie zapewniają API przeglądarki – zewnętrzne mechanizmy, które wykonują pracę „na boku”, poza głównym wątkiem Twojego skryptu.

Do najczęściej używanych należą m.in.:

  • setTimeout / setInterval – pozwalają odłożyć wykonanie funkcji w czasie lub cyklicznie ją powtarzać.

  • fetch – odpowiada za wysyłanie zapytań sieciowych i odbieranie odpowiedzi z serwerów.

  • addEventListener – umożliwia nasłuchiwanie na zdarzenia w interfejsie, np. kliknięcia, ruchy myszy czy wpisywanie tekstu w polach formularzy.

Kiedy wywołujesz jedno z tych API, przekazujesz do niego funkcję (callback). Następnie Twoje polecenie zostaje zarejestrowane przez odpowiedni mechanizm w przeglądarce. Przeglądarka wykonuje zadanie niezależnie od głównego wątku JavaScriptu (np. czeka określony czas, pobiera dane z sieci albo nasłuchuje zdarzeń DOM). Gdy zadanie jest gotowe, wynik (lub informacja o zdarzeniu) zostaje umieszczony w kolejce zadań – task queue albo microtask queue, w zależności od typu operacji. Panuje tam zasada FIFO (First In, First Out), czyli pierwsze wychodzi to, co pierwsze przyszło.

I dopiero wtedy do gry wkracza event loop, którego rolą jest koordynowanie powrotu tych wyników do Twojego kodu. Pętla zdarzeń nie wykonuje zadań sama w sobie – raczej „pilnuje porządku”, decydując, kiedy i które z gotowych operacji mogą trafić z kolejki na stos wywołań.

Poniższa animacja powinna zobrazować Ci cały proces.

Po wywołaniu setTimeout callback trafia do Web API, a po upływie czasu Event Loop przenosi go z kolejki zadań na stos, gdy ten jest pusty.

Po wywołaniu setTimeout callback trafia do Web API, a po upływie czasu Event Loop przenosi go z kolejki zadań na stos, gdy ten jest pusty.

Dzięki temu:

  • Twój skrypt może reagować na zdarzenia użytkownika bez blokowania całej aplikacji.

  • Wyniki z długotrwałych operacji (np. odpowiedź z serwera) trafiają do kodu dopiero wtedy, gdy JavaScript jest gotowy, by się nimi zająć.

  • Kolejność wykonywania zadań jest przewidywalna – event loop trzyma się określonych reguł (priorytetyzacji zadań), o których więcej opowiem poniżej.

Same API przeglądarki nie mogą od razu „wcisnąć” wyników swoich działań do Twojego kodu. Muszą poczekać, aż event loop uzna, że nadszedł na to odpowiedni moment. To właśnie dlatego np. setTimeout(fn, 0) nigdy nie wykona się absolutnie natychmiast – funkcja trafi do kolejki i zostanie obsłużona dopiero wtedy, gdy stos wywołań będzie pusty.

Task queue (makrotaski) i microtask queue (mikrotaski)

Na animacji widać było kolejkę makrotasków, ponieważ mój przykład zawierał jedynie callback z setTimeout. Zazwyczaj właśnie w ten sposób wyobrażam sobie działanie pętli zdarzeń — jako kolejkę podzieloną na makro i mikrotaski, przy czym mikrotaski mają wyższy priorytet.

Gdy przeglądarka zakończy operacje takie jak setTimeout, setInterval czy obsługę zdarzeń DOM, ich callbacki trafiają do kolejki zadań, zwanej też task queue albo kolejką makrotasków (co widać było na animacji). Event loop zagląda do niej tylko wtedy, gdy stos wywołań jest pusty.

Mikrotaski mają wyższy priorytet niż makrotaski. Do tej kolejki trafiają m.in. callbacki z Promise.then czy MutationObserver. Po opróżnieniu call stacka event loop w pierwszej kolejności obsługuje wszystkie mikrotaski, zanim przejdzie do zadań z głównej kolejki.

Zasady kolejności wykonywania zadań

  1. JavaScript wykonuje kod synchroniczny na stosie wywołań, aż stos będzie pusty.

  2. Następnie event loop sprawdza kolejkę mikrotasków i wykonuje je wszystkie, zanim przejdzie dalej.

  3. Gdy mikrotaski się skończą, pętla może pobrać pierwszy element z task queue i rozpocząć jego wykonanie.

Podsumowanie

Dzięki pętli zdarzeń możesz pisać aplikacje, które reagują na zdarzenia użytkownika, pobierają dane z sieci i wykonują wiele innych zadań, a mimo to pozostają responsywne. Kluczem jest zrozumienie, w jakiej kolejności wykonywane są operacje: najpierw kod synchroniczny, potem mikrotaski, a na końcu makrotaski. Zrozumienie event loopa pozwoli Ci przewidywać, jak Twoje aplikacje będą reagować na zdarzenia, i unikać typowych pułapek związanych z asynchronicznością. Warto wracać do tego tematu, zwłaszcza podczas debugowania lub optymalizacji kodu.

Avatar: Maciej Mikołajczak

Front-end Developer

Maciej Mikołajczak

mcj.mikolajczak@gmail.com

Podziel się na

Dodaj komentarz

Komentarze (0)

Brak komentarzy