Defer ve Async Arasındaki Fark Nedir?
Aynı script dosyasını iki farklı özellikle çağırabilirsiniz ve sayfanın hissi ciddi biçimde değişebilir. Bunun nedeni dosyanın kendisi değil, tarayıcının o dosyayla kurduğu zamansal ilişkidir. Defer ve async bu zaman çizelgesinde ayrılır. İkisi de “bu scripti HTML akışına biraz daha az zarar verecek şekilde yükle” gibi görünür; ama çalışma anı, sıra davranışı ve diğer dosyalarla ilişkileri aynı değildir.
JavaScript dosyalarını HTML dokümanına çağırmanın bedeli sadece dosya boyutu (kilobayt) değildir; asıl bedel tarayıcının okuma (parse) işlemine vurduğunuz darbedir. Düz bir <script src="..."> etiketi yazdığınızda tarayıcı, HTML ayrıştırmasını derhal durdurur, o dosyayı indirir, compile eder (derler), çalıştırır ve anca ondan sonra ekrandaki diğer DOM elemanlarını çizmeye devam eder. Bu yüzden render engelleyen kaynaklar sorununun baş aktörü her zaman kontrolsüzce içeriye çekilen JS'lerdir. Defer ve Async özellikleri, tam da HTML ayrıştırmasını (DOM parsing) bloklamadan JS çalıştırmanın iki farklı kronolojisidir.
Ekip performans analizine oturduğunda genelde "Önemsizleri async, önemlileri defer yapalım" gibi yüzeysel bir karara varır. Oysa teknik fark o dosyanın öneminden ziyade, sayfanın yapısıyla kurduğu bağımlılık zinciriyle ölçülür. Yanlış bir Async tercihi, menünüzün daha DOM yüklenmeden patlamasına, yanlış bir Defer tercihi ise asıl ihtiyaç duyulan bileşenin gereğinden geç reaksiyon vermesine zemin hazırlar.
Async hangi durumda uygundur?
Async (<script async>) komutu tarayıcıya "Bu dosyayı arkaplanda (asenkron) indirmeye başla, indiği an HTML ayrıştırmasını durdur ve derhal çalıştır" talimatı verir. En cezbedici tarafı, dosyanın boyutuna ve ağ hızına bağlı olarak indirme süresince DOM çizimini asla bloke etmemesidir.
Ancak asıl maliyet çalışma sırasındadır. Eğer ana kütüphaneniz 1 MB, eklentiniz 50 KB ise ve ikisine birden async verdiyseniz, 50 KB'lık dosya ağdan çok daha erken inip anında çalışabilir. Eklenti, henüz gelmemiş olan ana kütüphaneyi bulamadığı için "ReferenceError" fırlatıp konsolu kırabilir. Async ortamında ilk inen, ilk çalışır. Aralarında güvenli bir sıra garantisi yoktur.
<!-- Mantıklı ve güçlü bir `async` kullanımı -->
<!-- Sayfa çökse de, geç gelse de DOM'un umurunda olmaz -->
<script async src="https://www.google-analytics.com/analytics.js"></script>
<script async src="/assets/third-party-chat-widget.js"></script>
DOM hiyerarşisiyle ilgisi olmayan Google Analytics, Tag Manager veri ölçümleri (pixel) ya da alt taraftaki bağımsız sosyal medya buton scriptleri async ile işaretlenebilir. Ancak sitenin tasarımında bir sepet butonu, menü animasyonu veya login bileşeni varsa, bunlara async vermek kullanıcının o elementi tıklamadan önce betiğin hazır olmama ihtimalini göze almak demektir.
Defer neden daha öngörülebilirdir?
Defer (<script defer>) tıpkı Async gibi HTML ayrıştırmasını (parsing) bloke etmeden dosyayı ağ üzerinden indirmeye başlar. Ancak en büyük özelliği şudur: Dosya anında inse bile defer, "HTML ayrıştırması (DOMContentLoaded) tamamen bitene kadar" kendini beklemeye alır. Kodunuz ancak bütün görsel DOM iskeleti tarayıcıya hatasız çizildikten sonra sıraya girer.
Defer'i çoğu projede standart (safe-default) kılan asıl unsur sıraya sadakatidir. HTML dosyanızın üzerinde 5 tane defer etiketli dosya çağırdıysanız, 5 numaralı dosya 1 saniyede inse, 1 numaralı dosya 5 saniyede inse dahi; tarayıcı hepsi inene kadar bekler ve bu kodları yazılış sırasına göre art arda tetikler. Sisteminiz asla "Undefined" hatasına düşmez.
Vue/React ekosistemlerinden çıkmış projelerin haricindeki Vanilla veya hibrid sistemlerde, sitenin kendi navigasyon davranışları, slider ayarları, form validasyonları ve modal kütüphaneleri mutlak surette Defer şemsiyesine girmelidir. Çünkü "Site önce okunabilir, daha sonra tıklanabilir (Interactive) olmalıdır" felsefesi ancak bu kurguyla hayatta kalır.
Karar verirken hangi sorular sorulmalı?
Her kaynağa laboratuvar mantığıyla "hız" odaklı değil, "bozulma riski" odaklı yaklaşmalısınız. Bir dosyayı sıraya dizerken şu kural matrisi uygulanır:
- Bu dosya DOM üzerinde bir elementi modifiye ediyor mu? (Örn: Menüyü toggle eden buton) Evet ise: Mutlak surette
defer. - Bu dosya başka bir JS dosyasına bağımlı mı? (Örn: jQuery eklentisi) Evet ise:
defer. - Bu dosya sayfadaki kullanıcıdan tamamen izole bir veri mi çekiyor? (Örn: Reklam bannerı, sayfa başı istatistik tetikleyicisi) Evet ise:
async. - Görseli anında etkileyen bir iskelet operasyonu mu? (Örn: Sayfa hesaplanmadan ebatları ölçen inline script) O vakit etiket konmaz, ana thread'i bloke etmesi kasıtlı olarak istenir.
Genellikle takımların dynamic import yapısına geçmeye çabaladıkları projelerde altyapıdaki bu async/defer kırılımlarının anlaşılmadığı çok nettir. Zira sayfa açılırken defer olarak gelmeyen bir ana gövdenin içerisinden, tıklama anında dinamik bir async promise fırlatmak, uygulamanın durum yönetimini (state management) race-condition (yarış) tuzağına iter.
Modern <script type="module"> yapısını doğrudan kullandığınızda tarayıcı o componenti default olarak defer gibi ağaç sonuna itecektir. ECMAScript bileşen mimarisinin bu default koruması bile, kontrolsüz asenkron çalışmanın tehlikelerine karşı sistemin aldığı doğal bir garddır.
Sırf Lighthouse testlerinde "Sayfa çabuk yanıt versin" (Time to Interactive) değerini düşürmek adına, ana uygulamanın render logic'lerini async atayarak DOM ile asenkron yarışına sokmak, düşük segmentli telefonlarda eksik düğüm (missing node) faciaları üretir. Mühendislikte başarı, kodun donanıma çok hızlı yığılması değil, ekranda parçalanmadan kusursuz okunabilir olmasıdır.
Gerçek sayfada karışık yükleme matrisi nasıl kurulur?
Üretimde çoğu sayfa tek tip script kullanmaz. Bir yanda gezinme, form doğrulama ve modal davranışları gibi DOM'a bağlı kendi betikleriniz vardır; öte yanda analitik, reklam, sohbet veya A/B test kodları gibi bağımsız üçüncü taraf dosyaları bulunur. Sağlıklı yapı kurmak için bunların hepsine aynı etiketi vermek yerine görevlerine göre ayırmak gerekir. Kendi arayüzünüzü ayağa kaldıran çekirdek dosyalar çoğu durumda defer ile gelir. Birbirinden bağımsız, hata verse bile sayfanın okunmasını durdurmayacak üçüncü taraf parçalar ise async ile bırakılır.
Bu ayrım özellikle bağımlılık zincirlerinde önemlidir. Örneğin eski bir projede önce temel kütüphane, sonra onun eklentisi, sonra da sayfaya özel başlatma dosyası çağrılıyorsa; bu üçlü arasındaki sıra korunmalıdır. Burada async kullanımı çoğu zaman gereksiz risk üretir. Tam tersine, bağımsız bir reklam etiketi veya istatistik pikseli için defer seçmek de faydasız olabilir; çünkü dosyanın sıraya sadakati gereksizdir ve yalnızca daha geç çalışmasına neden olur. Yani doğru seçim "hangi dosya daha önemli" sorusundan değil, "hangi dosya hangi başka parçaya bağlı" sorusundan çıkar.
Bir diğer pratik başlık da satır içi küçük konfigürasyonlardır. Bazen harici dosya çalışmadan önce sayfaya çok küçük bir ayar nesnesi bırakmak gerekir: dil değeri, rota bilgisi, kullanıcı durumu veya özellik bayrağı gibi. Böyle birkaç satırlık kurulum kodu için ağır bir harici dosyayı erkene çekmek yerine, küçük konfigürasyonu satır içi bırakıp esas davranış dosyasını defer ile çağırmak daha dengelidir. Böylece kritik bilgi hazır olur, ama büyük JavaScript paketi HTML ayrıştırmasını durdurmaz.
Doğrulama tarafında da teori yetmez. Chrome DevTools üzerinde hem Network hem Performance kaydı açılıp dosyaların indirilme ve yürütülme anı birlikte incelenmelidir. DOMContentLoaded öncesinde çalışan beklenmedik bir script, uzun görev üreten üçüncü taraf dosyası veya bağımlılık sırasını bozan async seçimleri burada hemen görünür. İyi bir kurulumda kullanıcı önce düzgün bir iskelet görür, ardından etkileşim katmanı devreye girer; analitik ve ikincil araçlar ise bu akışı bozmadan arkadan yerini alır. Defer ile async ayrımının gerçek değeri tam olarak bu koordinasyonda ortaya çıkar.
Eski tarayıcı desteği verilen projelerde type="module" ve nomodule kombinasyonu da bu planın bir parçasıdır. Modern tarayıcılar modül scriptleri zaten defer benzeri davranışla ele alırken, eski tarayıcıya gönderilen geri uyum dosyası ayrı bir akış izler. Bu yapıda hangi dosyanın gerçekten kim için çalıştığını bilmeden yapılan async müdahaleleri, sadece belirli cihazlarda görülen ve teşhisi zor hatalar üretir. Bu yüzden script stratejisi her zaman hedef tarayıcı matrisinizle birlikte düşünülmelidir.