İçindekiler
JavaScript tek iş parçacığında çalışır. Bir sunucudan veri beklerken, bir dosya okunurken ya da bir zamanlayıcı dolarken tarayıcının donmaması için bu işlemler eşzamansız (asynchronous) yapılır: İşlem başlatılır, JavaScript başka işlere devam eder ve işlem bittiğinde sonuçla ilgilenilir.
Bu modelin nasıl yazıldığı, yıllar içinde üç aşamadan geçti.
1. Callback'ler
kullaniciGetir(5, function (hata, kullanici) {
if (hata) return goster(hata);
siparisleriGetir(kullanici.id, function (hata, siparisler) {
if (hata) return goster(hata);
detayGetir(siparisler[0].id, function (hata, detay) {
if (hata) return goster(hata);
ekranaYaz(detay);
});
});
});
Her adım bir öncekinin içine yazılır ve kod sağa doğru kayarak "callback cehennemi" denen piramide dönüşür. Her seviyede hata kontrolünü elle yapmak gerekir.
2. Promise'ler
Promise, henüz tamamlanmamış bir işlemin gelecekteki sonucunu temsil eden bir nesnedir. then ile zincirlenir:
kullaniciGetir(5)
.then(kullanici => siparisleriGetir(kullanici.id))
.then(siparisler => detayGetir(siparisler[0].id))
.then(detay => ekranaYaz(detay))
.catch(hata => goster(hata));
Piramit düzleşti ve tek bir catch bütün zincirin hatalarını yakalıyor. Ama mantık karmaşıklaştıkça (koşullar, döngüler, ara değişkenler) then zincirleri de okunması zor hâle geliyor.
3. async/await
async function detayiGoster() {
try {
const kullanici = await kullaniciGetir(5);
const siparisler = await siparisleriGetir(kullanici.id);
const detay = await detayGetir(siparisler[0].id);
ekranaYaz(detay);
} catch (hata) {
goster(hata);
}
}
Kod yukarıdan aşağıya, senkron kod gibi okunuyor. Arka planda hâlâ Promise'ler kullanılıyor; async/await yalnızca onları yazmanın daha okunaklı bir yolu.
asyncile işaretlenen bir fonksiyon her zaman bir Promise döndürür.await, yalnızcaasyncfonksiyonların içinde (ve modern ortamlarda modüllerin en üst seviyesinde) kullanılabilir. Promise tamamlanana kadar o fonksiyonun çalışmasını duraklatır, ama tarayıcıyı ya da sunucuyu dondurmaz.
fetch ile gerçek bir örnek
async function urunleriGetir() {
const cevap = await fetch('/api/urunler?kategori=kitap');
if (!cevap.ok) {
throw new Error(`Sunucu hatası: ${cevap.status}`);
}
return cevap.json();
}
Burada çok yaygın bir tuzak var: fetch, sunucu 404 ya da 500 döndürdüğünde hata fırlatmaz. Yalnızca ağ bağlantısı tamamen başarısız olduğunda hata fırlatır. Bu yüzden cevap.ok kontrolünü kendiniz yapmalısınız.
Sık yapılan hatalar
1. Gereksiz sıralı bekleme
// Yavaş: İkinci istek, ilki bitene kadar başlamaz
const kullanici = await kullaniciGetir(5);
const kampanyalar = await kampanyalariGetir();
Bu iki istek birbirine bağlı değil. Paralel çalıştırın:
const [kullanici, kampanyalar] = await Promise.all([
kullaniciGetir(5),
kampanyalariGetir(),
]);
Her biri 300 milisaniye sürüyorsa, ilk sürüm 600, ikincisi yaklaşık 300 milisaniye sürer.
2. Promise.all ile Promise.allSettled farkı
Promise.all, isteklerden biri bile başarısız olursa hemen hata verir ve diğerlerinin sonuçlarını kaybedersiniz. Birbirinden bağımsız isteklerin hepsinin sonucunu görmek istiyorsanız Promise.allSettled kullanın:
const sonuclar = await Promise.allSettled([haberler(), hava(), doviz()]);
for (const s of sonuclar) {
if (s.status === 'fulfilled') goster(s.value);
else console.warn('Bir bileşen yüklenemedi:', s.reason);
}
Böylece döviz servisi çökse bile haberler ve hava durumu yine gösterilir.
3. Döngüde await
// Her ürün bir öncekini bekler
for (const id of urunIdleri) {
await stokGuncelle(id);
}
Sıranın önemli olduğu durumlarda bu doğrudur. Önemli değilse paralel çalıştırın:
await Promise.all(urunIdleri.map(id => stokGuncelle(id)));
Ama bin ürünü aynı anda göndermek sunucuyu boğabilir. Çok sayıda istekte, istekleri 5'li ya da 10'lu gruplar hâlinde gönderin.
forEach ile await ise beklediğiniz gibi çalışmaz; forEach dönen Promise'leri beklemez:
// YANLIŞ: Döngü hemen biter, işlemler arkada başıboş devam eder
urunIdleri.forEach(async id => { await stokGuncelle(id); });
4. Yakalanmayan hatalar
async bir fonksiyonu await etmeden çağırırsanız ve içinde hata olursa, hata sessizce kaybolabilir ya da konsolda yalnızca "Unhandled promise rejection" uyarısı görünür. Her eşzamansız çağrının ya await edildiğinden ve bir try/catch içinde olduğundan ya da sonuna .catch() eklendiğinden emin olun.
5. Zaman aşımı yok
Cevap vermeyen bir sunucuya yapılan fetch isteği çok uzun süre bekleyebilir. Modern tarayıcılarda zaman aşımı eklemek kolay:
const cevap = await fetch('/api/rapor', { signal: AbortSignal.timeout(8000) });
Sonuç
async/await, eşzamansız kodu senkron kod kadar okunaklı yapar. Ama arka planda hâlâ Promise'lerin çalıştığını unutmayın. Bağımsız işleri paralel çalıştırın, fetch yanıtlarının durumunu kontrol edin ve hiçbir Promise'i başıboş bırakmayın.
Yorumlar
Henüz yorum yok. İlk yorumu siz yazın.