Promise, Gelecekteki Bir Değerin Yer Tutucusudur
JavaScript zaman alan bir iş yaptığında — bir ağ isteği, dosya okuma, zamanlayıcı beklemek gibi — sonucu sana hemen teslim edemez. Onun yerine elinize bir Promise tutuşturur: ileride var olacak bir değeri temsil eden bir nesne.
İlk console.log henüz pending (beklemede) durumundaki bir Promise gösteriyor. Yarım saniye sonra Promise çözüme ulaşıyor ve .then içindeki callback, gelen değerle birlikte çalışıyor. Aslında Promise'in kendisi sıradan bir nesne; işin sihirli tarafı, değer hazır olduğunda kendisini dinleyen herkese haber verebilmesi.
Promise'in Üç Durumu
Bir Promise her an şu üç durumdan birinde bulunur:
- pending — iş hâlâ devam ediyor. Henüz bir değer yok.
- fulfilled — iş başarıyla tamamlandı. Artık bir değer var.
- rejected — iş başarısız oldu. Bir hata mevcut.
Bir Promise, pending durumundan fulfilled ya da rejected durumuna yalnızca bir kez geçer ve sonsuza dek orada kalır. Yani bir Promise'i geri alamaz veya iki kez çözemezsin.
Promise.resolve(value) sana anında fulfilled durumda bir Promise verir; Promise.reject(error) ise anında reddedilmiş bir Promise döner. Bunlar hem testlerde hem de bazen sonucu hemen elinde olan bir fonksiyondan Promise döndürmek istediğinde çok işine yarar.
Değere Ulaşmak: .then ve .catch kullanımı
Bir Promise'in içindeki değeri doğrudan çıkarıp alamazsın — bunun yerine .then'e bir callback verirsin, Promise de değer hazır olduğunda o callback'i çağırır:
.catch(fn), Promise reject olduğunda devreye girer. Perde arkasında aslında .then(undefined, fn) yazmanın kısa yoludur. Zincirin sonuna koyacağın tek bir .catch(), üstteki adımların herhangi birinden gelen hatayı yakalar; her .then'in ardına bir tane eklemene gerek yok.
Promise Zincirleme: Her .then Yeni Bir Promise Döndürür
İşte çoğu kişinin takıldığı nokta burası. .then() yalnızca bir callback çalıştırmakla kalmaz — callback'ten dönen değere resolve olan yeni bir Promise döndürür. Zincirlemeyi mümkün kılan şey de tam olarak bu:
Her adım bir sonrakini besler. Bir .then callback'i Promise döndürdüğünde zincir, devam etmeden önce o Promise'in tamamlanmasını bekler; böylece asenkron adımlar temiz bir şekilde birbirine bağlanır:
Birbirini takip eden üç asenkron adım, hem de iç içe geçmeden. Aynı mantığı bir de callback'lerle yazdığını düşün; Promise'lerin neden bu kadar hızlı benimsendiğini o zaman daha iyi anlarsın.
Hatalar Zincir Boyunca Aşağı Düşer
Reddedilen bir Promise, bir .catch bulana kadar yolundaki tüm .then'leri atlar. Promise'lerde hata yönetiminin özü tam olarak bu:
.then içinde throw atmak, o .then'in döndürdüğü Promise'i reject eder. Sonraki .then bu reddi görür ve aşağıya aktarır, ta ki .catch onu yakalayana kadar. Genelde zincirin sonuna koyacağın tek bir .catch yeterli olur — hiç .catch koymazsan "unhandled promise rejection" uyarısı alırsın ki bundan kaçınmak istersin.
new Promise ile Kendi Promise'ini Oluşturmak
Çoğu zaman kütüphanelerin sana verdiği Promise'leri tüketirsin. Ama ara sıra Promise döndürmeyen bir şeyi — genelde eski tarz callback tabanlı bir API'yi — sarmalaman gerekir:
new Promise'e verdiğin fonksiyona executor deniyor. Bu fonksiyon iki parametre alır: resolve (başarılı sonuçla çağırırsın) ve reject (hata ile çağırırsın). Bunlardan yalnızca birini, yalnızca bir kez çağır. Sonraki çağrılar görmezden gelinir.
Başını ağrıtmayacak iki alışkanlık:
new Promise'i sadece Promise tabanlı olmayan bir şeyi sarmalıyorsan kullan. Eğer fonksiyon zaten bir Promise döndürüyorsa, onu olduğu gibireturnet.- Her zaman
rejectçağrısını bir string ile değil,Errornesnesiyle yap. Stack trace'i kaybetmeye değmez.
Paralel İşlemler: Promise.all ile eş zamanlı çalıştırma
.then zincirleri sırayla çalışır. Birbirinden bağımsız birden fazla asenkron işin aynı anda çalışmasını istiyorsan, doğru araç Promise.all:
Üç zamanlayıcı da eş zamanlı çalışır. Promise.all, tüm Promise'ler başarıyla tamamlandığında, sonuçları giriş sırasına göre bir dizi olarak döner. Toplam süre 900ms değil, yaklaşık 400ms olur.
Ama bir püf noktası var: Promise.all, içindeki Promise'lerden herhangi biri reject olduğu anda tüm işlemi reject eder ve diğer sonuçlar çöpe gider. Sayfayı render etmek için üç API çağrısının da dönmesi gerektiği gibi durumlarda bu tam olarak istediğimiz davranıştır. Ama her parça kritik değilse, allSettled kullanmak daha mantıklı.
Bazı Hataları Tolere Etmek: Promise.allSettled
Promise.allSettled, ister fulfilled ister rejected olsun, tüm Promise'lerin bitmesini bekler ve sana detaylı bir rapor verir:
Her sonuç şu şekilde bir nesne olur: { status: "fulfilled", value } ya da { status: "rejected", reason }. Kısmi başarının kabul edilebilir olduğu durumlarda çok işe yarar — bir grup olayı loglamak, bir sürü küçük resim (thumbnail) çekmek ya da birbirinden bağımsız health check'leri çalıştırmak gibi.
Bilmenizde fayda olan iki kombinatör daha var:
Promise.race([...])— Promise'lerden ilki hangi şekilde sonuçlanırsa (başarı ya da hata) onunla tamamlanır. Timeout senaryoları için birebir.Promise.any([...])— ilk başarılı sonuçla fulfill olur, reject'leri yok sayar. Ancak tüm Promise'ler reject olursa kendisi de reject olur.
Promise'ler Her Zaman Asenkrondur
Zaten resolve edilmiş bir Promise bile .then callback'ini asenkron olarak çağırır — asla senkron değil, asla aynı tick içinde değil:
Çıktı sırası şöyle: önce, sonra, anlık. .then içine verdiğin callback, önce mevcut senkron kodun bitmesini bekler, sonra microtask kuyruğunda çalışır. "Bir Promise callback'i asla senkron çalışmaz" kuralı işte bu yüzden var — ve Promise'leri senkron kodla birlikte kullandığında davranışın tahmin edilebilir olmasını sağlayan da bu: senkron kod her zaman önce biter.
Sırada: async/await
.then zincirlemesi işini görüyor, ama iki üç adımı geçtiğinde kod merdiven gibi uzamaya başlıyor. async/await, Promise'lerin üzerine oturan bir söz dizimi; aynı mantığı sanki senkron kod yazıyormuşsun gibi ifade etmeni sağlıyor — hatalar için try/catch, ara değerler için de sıradan değişkenler kullanabiliyorsun. Bir sonraki konumuz bu.
Sıkça Sorulan Sorular
JavaScript'te Promise nedir?
Promise, henüz elinizde olmayan bir değeri temsil eden bir nesnedir — genellikle bir ağ isteği gibi asenkron bir işlemin ileride dönecek sonucunu. Her zaman üç durumdan birinde bulunur: pending, fulfilled ya da rejected. Sonuca ulaşmak için .then() ve .catch() ile callback'ler eklersiniz.
then ile catch arasındaki fark nedir?
.then(onFulfilled) Promise başarıyla çözüldüğünde çalışır ve çözümlenen değeri alır. .catch(onRejected) ise Promise (ya da zincirdeki önceki herhangi bir Promise) reddedildiğinde devreye girer ve hatayı yakalar. Zincirin sonuna eklediğiniz tek bir .catch(), yukarıdaki tüm adımlarda oluşan hataları toplu olarak yakalar.
Promise.all ne işe yarar?
Promise.all([p1, p2, p3]) bir Promise dizisi alır ve tüm çözümlenen değerleri içeren tek bir Promise döner — ama bunu ancak dizideki her Promise başarıyla tamamlandığında yapar. İçlerinden biri reddedilirse tamamı anında reject olur. Hataları da dahil tüm sonuçları toplamak istiyorsanız Promise.allSettled kullanın.
Promise mi kullanmalıyım, async/await mi?
Aslında ikisi aynı mekanizma — async/await, Promise'lerin üzerine yazılmış bir söz dizimi. Yeni yazılan kodların çoğu async/await ile daha okunaklı oluyor, ama yine de Promise döndürüyorsunuz, hataları yine try/catch veya .catch() ile yakalıyorsunuz ve paralel işler için yine Promise.all'a başvuruyorsunuz. Promise'in nasıl çalıştığını anlarsanız async/await da yerine oturuyor.