Gdy strona ładuje dużo JavaScriptu, różnica między momentem pobrania pliku a momentem jego wykonania decyduje o tym, czy interfejs pojawi się szybko i czy kod uruchomi się w przewidywalnej kolejności. To praktyczne porównanie defer vs async pokazuje, kiedy przeglądarka wstrzymuje parsowanie HTML, kiedy uruchamia skrypt od razu, a kiedy czeka do końca budowy DOM. Dobrze dobrany atrybut potrafi uprościć kod, przyspieszyć stronę i uniknąć błędów, które na produkcji są wyjątkowo kosztowne.
Co warto zapamiętać na start
- async pobiera skrypt równolegle i uruchamia go natychmiast po pobraniu, bez gwarancji kolejności.
- defer pobiera skrypt równolegle, ale wykonuje go dopiero po sparsowaniu HTML, w kolejności występowania.
- Oba atrybuty mają sens głównie przy zewnętrznych skryptach klasycznych, nie przy inline.
-
DOMContentLoadedczeka na skrypty zdefer, ale nie czeka naasync. -
type="module"zachowuje się jak odroczony domyślnie, więc często nie trzeba dodawaćdefer.

Najważniejsze różnice w jednym spojrzeniu
Jeśli chcesz porównać te atrybuty bez wchodzenia od razu w szczegóły implementacyjne, najlepiej spojrzeć na trzy elementy: moment pobrania, moment wykonania i kolejność. W praktyce chodzi o to, czy skrypt ma być ważniejszy od HTML, czy raczej ma poczekać, aż dokument będzie gotowy.
| Cecha | async |
defer |
|---|---|---|
| Pobieranie pliku | Równolegle z parsowaniem HTML | Równolegle z parsowaniem HTML |
| Moment wykonania | Jak tylko skrypt zostanie pobrany | Po zakończeniu parsowania dokumentu, tuż przed DOMContentLoaded
|
| Kolejność uruchomienia | Nie jest gwarantowana | Jest zachowana zgodnie z kolejnością w dokumencie |
Wpływ na DOMContentLoaded
|
Nie blokuje tego wydarzenia | Opóźnia je do czasu wykonania skryptu |
| Najlepsze zastosowanie | Skrypty niezależne od DOM i od innych plików | Skrypty zależne od DOM albo od innych skryptów |
Gdy patrzę na ten zestaw, zwykle upraszczam decyzję do jednego pytania: czy ten kod może uruchomić się w dowolnym momencie, czy musi czekać na strukturę strony. Od odpowiedzi zależy praktycznie cały wybór. W kolejnym kroku rozkładam to na faktyczny przebieg ładowania w przeglądarce.
Jak przeglądarka interpretuje async i defer podczas ładowania
W klasycznym scenariuszu zewnętrzny skrypt bez żadnego atrybutu zatrzymuje parsowanie HTML. To ważne rozróżnienie: przeglądarka nie tylko czeka na plik, ale też przerywa budowanie drzewa DOM, co przy większych zasobach potrafi wyraźnie opóźnić wyświetlenie strony.
- Przeglądarka czyta HTML od góry do dołu.
- Jeśli natrafi na zwykły zewnętrzny
, zatrzymuje parsowanie, pobiera plik i dopiero potem go wykonuje. - Jeśli skrypt ma
async, pobieranie odbywa się równolegle, a wykonanie następuje natychmiast po pobraniu, nawet jeśli HTML nie został jeszcze w całości przeanalizowany. - Jeśli skrypt ma
defer, pobieranie też odbywa się równolegle, ale wykonanie czeka do końca parsowania dokumentu. -
DOMContentLoadedpojawia się dopiero po wykonaniu skryptów odroczonych, ale nie czeka na skrypty asynchroniczne.
W praktyce oznacza to, że async jest bardziej agresywny: daje skryptowi możliwość wejścia do gry od razu. defer jest spokojniejszy i przewidywalny, bo pozwala HTML-owi dokończyć pracę, a potem uruchamia kod w stabilnym momencie. Dla własnych komponentów front-endowych to zwykle bezpieczniejszy wybór. Następny krok to decyzja, kiedy który wariant naprawdę się opłaca.
Kiedy wybrać async, a kiedy defer
Ja zwykle traktuję async jako rozwiązanie dla kodu, który ma być samowystarczalny. Jeśli skrypt nie potrzebuje DOM-u, nie zależy od kolejności uruchomienia innych plików i może pojawić się w dowolnym momencie, to właśnie ten atrybut ma najwięcej sensu.
Wybierz async, gdy skrypt jest niezależny
- Analityka i tagi pomiarowe.
- Widgety zewnętrzne, które same zarządzają swoim stanem.
- Skrypty reklamowe lub testowe, które nie powinny blokować głównej treści.
- Elementy, których opóźnienie nie psuje działania interfejsu.
To dobre miejsce na async, bo zysk polega nie tylko na braku blokowania HTML, ale też na tym, że strona nie czeka na kod, który nie jest krytyczny dla użytkownika. Jednocześnie trzeba zaakceptować ryzyko: jeśli taki skrypt zależy od innego pliku, kolejność może się rozjechać.
Przeczytaj również: npm init w Node.js - Jak zacząć projekt bez błędów?
Wybierz defer, gdy kod zależy od DOM lub innych skryptów
- Nawigacja, menu, zakładki i inne elementy interfejsu, które odwołują się do istniejących już węzłów DOM.
- Własne bundle aplikacji, które inicjalizują komponenty po załadowaniu strony.
- Skrypty, które muszą działać w określonej kolejności, na przykład biblioteka najpierw, a kod aplikacji później.
- Logiczne inicjalizacje formularzy, walidacja i zachowania zależne od gotowej struktury dokumentu.
defer daje tu więcej kontroli, bo eliminuje przypadkowość kolejności. Jeśli kilka plików ma współpracować, wolę właśnie ten wariant albo moduły ES. Dzięki temu unikam klasycznego błędu: „czasem działa, czasem nie działa”, który zwykle wynika z losowego momentu wykonania skryptu. To prowadzi prosto do kolejnej kwestii, czyli najczęstszych potknięć.
Najczęstsze błędy, które psują efekt
Przy tych atrybutach problem rzadko leży w samych przeglądarkach. Zazwyczaj to kod jest źle dopasowany do sposobu ładowania. Poniżej są błędy, które widzę najczęściej, i które najłatwiej wyłapać już na etapie przeglądu kodu.
-
Używanie async dla skryptów zależnych od kolejności
Jeśli jeden plik zakłada, że drugi już się wykonał,
asyncpotrafi to rozbić. To szczególnie częste przy starszych integracjach, gdzie biblioteka i wtyczka są ładowane osobno. -
Zakładanie, że defer działa na inline
Dla skryptów osadzonych bez
srcten atrybut nie daje oczekiwanego efektu. W praktyce liczą się przede wszystkim zewnętrzne pliki. -
Dodawanie obu atrybutów „na wszelki wypadek”
To nie jest neutralna kompozycja. Jeśli oba są obecne, przeglądarka zachowuje się tak, jakby działał tylko
async, więc nie dostajesz pożądanego kompromisu. - Mylenie DOMContentLoaded z pełnym załadowaniem strony To wydarzenie oznacza, że dokument został sparsowany i skrypty odroczone się wykonały. Nie oznacza to jeszcze końca ładowania obrazów, ramek czy wszystkich zasobów.
-
Trzymanie się starego nawyku „na koniec body i już”
To czasem działa, ale nie rozwiązuje problemu kolejności ani nie daje takiej czytelności jak świadomy wybór między
asyncidefer.
Najgroźniejszy błąd jest prosty: skrypt wygląda na mały i nieszkodliwy, ale ukrywa zależność od DOM-u albo od innego pliku. Wtedy losowy moment wykonania staje się problemem produkcyjnym. Z tego powodu warto jeszcze spojrzeć na nowoczesne moduły, bo one zmieniają reguły gry.
Jak działają moduły ES i dlaczego często nie potrzebują defer
Jeśli pracujesz w nowoczesnym kodzie, bardzo możliwe, że zamiast klasycznego używasz type="module". To ważne, bo moduły ES zachowują się jak skrypty odroczone domyślnie. Innymi słowy, w wielu przypadkach dodatkowy defer po prostu nic już nie wnosi.
-
type="module"oznacza, że skrypt jest pobierany równolegle i wykonywany po parsowaniu dokumentu. -
deferna module nie daje dodatkowego efektu, bo moduły i tak są odroczone. -
asyncna module zmienia zachowanie na bardziej agresywne, czyli skrypt uruchamia się natychmiast po pobraniu. - W przypadku modułów kolejność importów kontrolujesz przede wszystkim przez samą strukturę importów, a nie przez przypadkowe ustawienia w HTML.
- Przy większych aplikacjach przydaje się też wcześniejsze preładowanie modułów, jeśli mają być potrzebne natychmiast po starcie.
To właśnie tutaj widać, że wybór nie sprowadza się do prostego „szybciej albo wolniej”. Chodzi o to, kiedy kod ma się uruchomić względem DOM i innych zależności. Jeśli projekt startuje dziś od modułów, ja traktuję je jako domyślny punkt wyjścia, a dopiero potem decyduję, czy któryś entry point naprawdę wymaga async. Na koniec zostaje najprostsza reguła wyboru, którą stosuję najczęściej.
Jedna reguła decyzyjna, która upraszcza wybór
- Jeśli skrypt dotyka DOM-u, korzysta z innych plików albo musi wykonać się w określonej kolejności, wybierz
defer. - Jeśli skrypt jest niezależny i może uruchomić się w dowolnym momencie, wybierz
async. - Jeśli używasz
type="module", załóż zachowanie zbliżone dodeferi dodawajasynctylko wtedy, gdy naprawdę akceptujesz mniej przewidywalny moment wykonania.
W praktyce różnica między tymi atrybutami nie polega na „magicznej optymalizacji”, tylko na kontroli nad kolejnością i momentem startu kodu. Jeśli mam wybrać bez długiego zastanawiania się, dla własnego front-endu zwykle stawiam na defer, a async zostawiam dla rzeczy naprawdę niezależnych. To daje stabilniejsze zachowanie, mniej losowości i mniej nocnych poprawek po tym, jak pozornie drobny skrypt przestaje działać po stronie użytkownika.