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)
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/catchile. - 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:
- Mümkünse birbirinden bağımsız işleri paralel başlatın.
Promise.all()kullanabileceğiniz yerde seri await yapmayın.setTimeout(..., 0)ile sıralama garantisi vermeye kalkmayın.- Error handling’i en sona bırakmayın.
- 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.
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?**
Bu içerik işinize yaradı mı?
Benzer içerikleri kaçırmamak için beni sosyal medyada takip edin.


