Programlama

JavaScript’te Async Mantığı: Event Loop’u Gerçekten Anlamak

Kendi deneyimimden konuşuyorum, JavaScript async meselesi ilk bakışta biraz sihir gibi duruyor. Hani Promise.then() neden setTimeout‘tan önce çalışıyor, await niye “sonra” dönüyor. Kod satır satır akıyormuş gibi görünüyor… İşin aslı şu ki, ortada sihir yok; gayet düzenli bir mekanizma var. Sadece çoğu kişi o mekanizmanın kaportasını hiç açmıyor.

Açık konuşayım. Yıllar önce bunu ilk kez çözmeye çalışırken epey afallamıştım — gerçekten (ki bu çoğu kişinin gözünden kaçıyor). 2019’un sonlarında, İstanbul’daki küçük bir fintech projesinde çalışırken bir callback zinciri yüzünden uygulama resmen geveleyip durmuştu; o gün “async biliyorum” sandığım şeyin aslında sadece yüzeyin ince bir tabakası olduğunu fark ettim, nasıl desem, biraz utanmıştım açıkçası. Bu yazıda da tam oraya gireceğiz, lafı uzatmadan ama gerektiği kadar detay vererek.

Asenkronluk Neden Bu Kadar Karışık Görünüyor?

JavaScript tek iş parçacıklı çalışıyor. Yani aynı anda iki farklı işi gerçekten yürütmüyor. Sıraya koyup hallediyor. Ama tarayıcı tarafında işler bununla bitmiyor çünkü timer’lar, ağ istekleri, DOM olayları ve benzeri şeyler JavaScript motorunun dışındaki katmanlara bırakılıyor, ve işte tam burada kafa karışmaya başlıyor — kodun kendisi tek sıra gidiyor ama arka planda başka oyuncular sahneye çıkıyor.

İşte tam da bu noktada devreye giriyor.

2024 Nisan’ında Ankara’daki bir e-ticaret ekibine kısa süreli danışmanlık verirken tam bu konuyu anlattım. Ekibin biri “0 ms verdik bir düşüneyim… ya, neden hemen çalışmıyor?” diye sordu. Çok klasik. Cevap basit değil ama net: setTimeout(..., 0) “hemen şimdi” demek değil. “Sıran gelince bakılır” demek.

Bence asıl mesele şu: JavaScript’in davranışını ezberlemek yerine kuyruğu ve önceliği anlamak gerekiyor. Çünkü async dediğimiz şey tek başına bir özellik değil — birkaç yapı taşının üst üste binmiş, birbiriyle konuşan hali.

Call Stack ne yapıyor?

İnanın, Call Stack, çalışan fonksiyonların tutulduğu yığın yapısıdır. LIFO mantığıyla çalışır; en son giren ilk çıkar. Bir fonksiyon çağrıldığında stack’e eklenir, işi bittiğinde çıkarılır. Basit örneklerde çok belli olmaz ama karmaşık yapılarda her şeyin omurgası burasıdır.

function ana() {
a();
}
function a() {
b();
}
function b() {
console.log("merhaba");
}
ana();

Bu örnekte sıralama nettir: önce ana(), sonra a(), ardından b(). Hepsi stack üzerinden ilerler. Buradaki düzen iyi hoş ama uzun süren bir iş gelince problem çıkmaya başlıyor — çünkü stack dolu kaldığı sürece başka hiçbir şey ileri gitmiyor, sıfır (en azından benim deneyimim böyle)

💡 Bilgi: Call Stack’i mutfaktaki tezgâh gibi düşünün. Aynı anda üç yemeği pişirmiyorsunuz; biri bitmeden diğeri için alan açılmıyor.

Kuyruklar İşe Nasıl Karışıyor?

Bakın, Tarayıcı ya da runtime ortamı bazı işleri JavaScript motorundan alıp dış servislerle yönetiyor. Mesela zamanlayıcılar Web API tarafına giderken ağ çağrıları da benzer biçimde arkada bekletiliyor (kendi tecrübem). Sonuç hazır olduğunda callback doğrudan stack’e atılmıyor; — itiraz edebilirsiniz tabi — önce uygun kuyruğa düşüyor. Siz hiç denediniz mi? İki ayrı kuyruk burada önemli hale geliyor: görev kuyruğu ve mikro görev kuyruğu (eh, fena değil)

Evet, isimler teknik duruyor. Ama mantık günlük hayatta sıraya girmekten farksız — mikro görev kuyruğu daha öncelikli kasa gibi, normal görev kuyruğu ise sıranın biraz daha gerisinde kalıyor.

Kavram Nerede Kullanılır? Sıralama Davranışı
Task Queue / Macrotask Queue setTimeout, DOM olayları, setInterval Mikro görevlere göre sonra çalışır
Microtask Queue .then(), .catch(), .finally(), queueMicrotask() Kuyruktaki ilk öncelik genelde buradadır

Bunu ilk kez kendi test ortamımda denediğimde — Mart 2023’te Kadıköy’deki ev ofisimde, kahvem soğurken — ufak bir not aldığımı hatırlıyorum: “Promise varsa timeout geri düşer.” Bu not kulağa komik geliyor olabilir ama pratikte inanılmaz işe yaradı, çünkü debug ederken beyniniz size ciddi oyunlar oynayabiliyor.

Neden Promise.then önce çalışıyor?

Cevap kısa: çünkü mikro görev kuyruğunda bekliyor ve event loop bu kuyruğa ekstra dikkat ediyor. Uzun cevap ise şöyle — call stack boşaldığında event loop sadece normal task queue’ya bakmaz, önce mikro görevleri tüketir, çoğunlukla hepsini tek seferde. O yüzden Promise zincirleri bazen beklediğinizden çok daha hızlı görünür.

Promise’lar “arka planda gizlice çalışan büyüler” değil; sadece normal task’lardan daha yüksek öncelikli kuyrukta bekleyen işlerdir.

Aynı Kodda Neler Oluyor?

Aşağıdaki örnek küçük görünür ama davranışı baya öğreticidir:

console.log("A");
setTimeout(() => {
console.log("B");
}, 0);
Promise.resolve().then(() => {
console.log("C");
});
console.log("D");

Ekranda sıralama büyük ihtimalle A D C B olur (ilk duyduğumda inanamadım). Neden? Önce senkron kod biter, — ki bu tartışılır — sonra microtask queue içindeki Promise işi çözülür, en son da timeout callback’i gelir (en azından benim deneyimim böyle). Kısacası setTimeout burada davetli misafir gibi bekliyor… Promise ise biraz torpilliydi diyelim!

  • Senkron kod her zaman önce tamamlanır.
  • Mikro görevler normal task’lardan önde gelir.
  • Mikro task sayısı çok artarsa UI gecikebilir.
  • Tatlı dursa da yanlış kullanılırsa performans hissini bozar.

Gel gelelim küçük proje ile kurumsal sistem arasında fark var mı? Var tabi. Küçük bir startup’ta çoğu ekip bunu ancak bug çıkınca fark ediyor; enterprise tarafta ise loglama. Gözlemlenebilirlik sayesinde sorun daha erken yakalanıyor. Ben geçen sene Haziran’da Berlin merkezli orta ölçekli bir SaaS firmasında incelediğim panelde şunu gördüm: tek bir yoğun promise zinciri bile kullanıcı etkileşimini ciddi biçimde zayıflatabiliyordu. İşte o an “async bilgisi teori değil ihtiyaç” cümlesi kafamda iyice oturdu. Daha fazla bilgi için Agentforce Script: Salesforce’un AI Ajanlarına Getirdiği Fren Pedalı yazımıza bakabilirsiniz.

Bunu biraz açayım.

`async/await` Gerçekte Ne Yapıyor?

async/await aslında yeni bir çalışma modeli icat etmiyor. Var olan Promise tabanlı sistemi daha okunur hale getiriyor, o kadar. Yani await gördüğünüz yerde motor stop etmiyor — sadece o noktada kontrolü bırakıp devam etmeyi planlıyor, arkaya bir not bırakıp geri dönüyor gibi düşünün. Bu konuyla ilgili Bir Haftada AI Fatura Üreticisi: Hızlı SaaS Dersi yazımıza da göz atmanızı tavsiye ederim.

Bir de şu var: await kullanınca kod senkronmuş gibi okunuyor diye herkes rahatlıyor. Ama alt tarafta yine Promise akışı dönüyor. O yüzden bazen hata ayıklarken insanlar yanıltıcı şekilde “burada blokladı” sanabiliyor. Hayır, bloklamadı; sadece akışı bölüp sonraya erteledi. İnce ama önemli fark.

Şunu fark ettim: Ben bunu özellikle Ocak 2024’te İzmir’de yaptığım küçük performans incelemesinde tekrar gördüm. Bir geliştirici arkadaşımın yazdığı async fonksiyonda gereksiz yere peş peşe await vardı — üç tane bağımsız işi sırayla bekliyordu, halbuki paralel gidebileceklerdi. Hani ne farkı var diyorsunuz, değil mi? Küçük ayrıntı gibi durur ama üretimde maliyet olarak geri döner, mutlaka.

Durun, bir saniye.

Nerede fayda sağlar, nerede tökezler?

Avantaj kısmı net: okunabilirlik yükselir, zincirleme .then() cezası azalır, bakımı kolaylaşır. Dezavantaj kısmı ise şu — çok fazla await ile gereksiz seri akış kurarsanız hız kaybedersiniz. Yani araç güzel, ama yanlış vitesle sürerseniz pek tat vermez. Daha fazla bilgi için Mülakatta Çaktırılan 7 Yazılımcı Hatası: Kaçın yazımıza bakabilirsiniz.

İşin özü:

  • Bağımsız işlemleri birlikte başlatın.
  • Birbirine bağlı işleri sırayla await edin. — bunu es geçmeyin
  • Hata yönetimini unutmayın; özellikle try/catch ile.
  • Uzun zincirlerde ölçüm yapın, tahmin etmeyin.

Bir dakika, şunu da ekleyeyim: performans sorunu yaşayan ekiplerin yarısında mesele altyapı değil, yanlış async kurgusu oluyor. E tabi herkes ilk etapta suçu sunucuya atmayı seviyor.

Dikkat Etmeniz Gereken Pratik Noktalar

Async dünyasında en sık yapılan hata “her şeyi await etmek.” Bu alışkanlık masum görünüyor ama bağımsız operasyonları gereksiz yere kilitliyor. İkinci hata, promise hatalarını sessizce yutmak. Üçüncüsü de task/microtask ayrımını hiç umursamamak. Maalesef.

Bakın şimdi, küçük uygulamalarda bunlar belki göze batmaz. Ama büyüdükçe kırılma yaratıyor. En çok da frontend tarafında animasyon, input gecikmesi ve API yanıtlarını aynı pencerede yaşarsanız — yani her şeyin aynı anda döndüğü o karmaşık ekranlarda — küçük ihmaller bile kullanıcı tarafından hissedilir hale geliyor, farkında olun ya da olmayın.

Benim sahadan öğrendiğim mini kontrol listesi şöyle:

  1. Mümkünse birbirinden bağımsız işleri paralel başlatın.
  2. Promise.all() kullanabileceğiniz yerde seri await yapmayın.
  3. setTimeout(..., 0) ile sıralama garantisi vermeye kalkmayın.
  4. Error handling’i en sona bırakmayın.
  5. Kritik UI işlerinde mikro görev patlamasına dikkat edin.

Neyse uzatmayalım, mantık şu: async görünüşte rahatlatıcıdır, ama disiplinsiz kullanırsanız ters teper. Beklediğim kadar değildi dediğim yer tam burasıydı — özellikle yeni başlayanların çoğu sözdizimine odaklanıp akışa odaklanmıyor. Tam da öyle.

💡 Bilgi: Eğer aynı anda birkaç isteğin sonucuna ihtiyacınız varsa, önce hepsini başlatıp sonra topluca beklemek çoğu zaman daha verimli olur. Tek tek await etmek bazen fiilen sıra numarası almak gibidir.

Bazı projelerde bu fark küçücük görünür; bazılarında ise sayfa açılışını ciddi etkiler (buna dikkat edin). Kısacası, en çok da içerik ağırlıklı sitelerde veya dashboard ekranlarında hissedilir — oradan biliyorum.

Aslında, Teknik okurlar için bağlantılı okumalar faydalıdır. Mesela frontend temellerini tazelemek isteyen biri için şu yazımız iyi gider: JavaScript’te Temel Problemler: Küçük Sorular, Büyük Kazançlar.

Güvenlik ve istemci tarafındaki garip davranışlara merakı olanlar için de ilginç bağlantılar var. İlginç, değil mi? SVG içine saklanan riskleri anlatan analizimiz burada işe yarar olabilir: Pixel Boyutunda Kandırmaca: SVG İçine Saklanan Hırsızlık.

Kurumsal süreç tarafını merak eden okurlar için benzer disiplin yaklaşımını anlatan şu yazıya da göz atabilirsiniz: Veritabanı Gözlemlenebilirliği: SQL’den Buluta Tam Görünürlük.

Sıkça Sorulan Sorular**

Q1 para?**

Aşkın KILIÇ

20+ yıl deneyimli Azure Solutions Architect. Microsoft sertifikalı bulut mimari ve DevOps danışmanı. Azure, yapay zekâ ve bulut teknolojileri üzerine Türkçe teknik içerikler üretiyor.

AZ-305AZ-104AZ-500AZ-400DP-203AI-102

Bu içerik işinize yaradı mı?

Benzer içerikleri kaçırmamak için beni sosyal medyada takip edin.

Haftalık Bülten

Her pazar özenle seçilmiş teknoloji yazıları doğrudan e-postanıza gelsin.

← Onceki Yazi
Agentforce Script: Salesforce’un AI Ajanlarına Getirdiği Fren Pedalı
Sonraki Yazi →
Apple ile Samsung Arasındaki Veri Savaşı: Antitröst Dosyasında Yeni Perde

Yorum Yaz

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Haftalık Bülten

Azure, DevOps ve Yapay Zeka dünyasındaki en güncel içerikleri her hafta doğrudan e-postanıza alın.

Spam yok. İstediğiniz zaman iptal edebilirsiniz.
📱
Uygulamayı Yükle Ana ekrana ekle, çevrimdışı oku
Kategoriler
Ara
Paylaş
İçindekiler
← Agentforce Script: Salesforce’...
Apple ile Samsung Arasındaki V... →
📩

Gitmeden önce!

Her pazar özenle seçilmiş teknoloji yazıları ve AI haberleri doğrudan e-postanıza gelsin. Ücretsiz, spam yok.

🔒 Bilgileriniz güvende. İstediğiniz zaman ayrılabilirsiniz.

📬 Haftalık bülten: Teknoloji + AI haberleri