Javascript Promise Yapıları Nedir ve Nasıl Çalışır?
Daha performanslı, bloklanmayan yazılım ürünleri geliştirmek ve kullanıcı memnuniyetini artırmak için asenkron işlemlere ihtiyaç duyarız. Yazılım dillerinin farklı konseptleri ve ihtiyaçlarına uygun farklı anahtar kelimeleri ve yapıları bulunur. JavaScript dilinde asenkron işlemleri gerçekleştirebilmek için Promise yapısını öğrenmek gereklidir.
Promise yapılarını anlamakta zorlanıyorsanız, şu soruları kendinize sorabilirsiniz:
- Fonksiyonel programlama yapısını biliyor muyum?
- JavaScript temellerine hakim miyim?
- Asenkron programlama kavramını biliyor muyum ve neden ihtiyaç duyulduğunun farkında mıyım?
- Callback fonksiyonlarını biliyor muyum ve rahatlıkla kullanabiliyor muyum?
Bu sorulara cevabınız evet ise, Promise yapılarını incelemeye başlayabilirsiniz. Hayır ise, bu akış diyagramının takıldığınız noktasına geri dönüp halletmeye çalışmanızı öneririm.
Callback Fonksiyon Nedir?
Callback fonksiyonlar bir fonksiyonu diğer bir fonksiyona argüman olarak verebilmek için kullanılan yapılarıdır. Bir işlem tamamlandığında başka bir şey de yapılmasını istiyorum dediğimiz zaman callback kullanmamız gerekiyor.
Javascript senkron çalışır ve single thread’dir ama kullanım alanları ve doğası gereği asenkron işlemlerle sık sık karşılaşırız. Uzun süren işlemlerimizin tamamlanmasını beklemeden diğer işlere devam etmek isteriz ve bloklanma yaşamamak isteriz. Bunun için bir iş çalışırken yani bir kod satırı okunduğunda uzun süren işlemin tamamlanmasını beklemeden diğer satıra geçmeliyiz ancak alt satırlarda uzun süren işlemin çıktısına bağlı bir işlem varsa kodumuz istediğimiz gibi çalışmayacaktır.
Peki uzun süren işler tamamlandığında, bu uzun süren işlemlere bağımlı olan diğer işler bu uzun süren işin sonucunu, tamamlandığını, hata alıp almadığını nasıl bilecek? İşte burada callback devreye girer ve javascript bize callback tabanlı asenkron programlamayı sunar.
Dikkat ederseniz setTimeout, setInterval, fetch gibi asenkron javascript fonksiyonları hep callback fonksiyon alırlar, sebebinden az önce bahsetmiş olduk.
Senaryo
Web uygulamamıza dosya yüklemi işlemi var ancak bu işlem uzun sürdüğü için kullanıcıyı donmuş bir ekranla karşılamak yerine yükleme anında geri bildirim vermek istiyoruz. Bunun için yüklenen boyuta göre işlemin yüzde kaçının bittiğini ekrana basmak istiyoruz.
Adım 1:
Callback alan bir upload fonksiyonu yazalım. Bu upload fonksiyonunun bir anlık değeri bir de işin alacağı bütün zamanı gösteren total değeri olsun. Bunu bir dosyanın upload edilen boyutu ile dosya boyutu gibi düşünebiliriz.
Upload fonksiyonunun upload edilen boyutu değiştikçe bir callback çağırsın ve bu callback’e anlık aktarılan boyut değerini ve total değeri versin böylece upload fonksiyonuna bir callback verdiğimizde bu callback fonksiyon iki parametre ile gelecek ve bu parametreler üzerinde işlemin yüzde kaçının tamamlandığını bulabileceğim.
function upload(callback) {
var current = 0;
var total = 5;
var interval = setInterval(() => {
current += 1;
callback(current, total);
if (current == total) clearInterval(interval);
}, 1000);
}
Burada anlık değeri 0 ve tamamlanma değeri 5 olan bir işlem var.
Bu işlem saniye başı 1 birim artıyor ve her artışta callback’i iki argüman ile tetikliyor.
Adım 2:
Upload fonksiyonunu bir callback argümanı geçerek çağıralım.
upload((current, total) => {
progress.innerHTML = current / total * 100 + "%";
});
Sonuç olarak
Tek iş parçacığı üzerinde çalışan javascript ile asenkron işlemleri gerçekleştirebilmemiz için ihtiyacımız olan callback fonksiyonlarını başka bir fonksiyonu argüman olarak alan higher order -yüksek seviyeli- fonksiyonlara nasıl geçeceğimizi ve ana fonksiyonun nasıl çalıştığını görmüş olduk. Uygulama kodlarına şuradan da erişebilirsiniz.
Promise Nedir?
Temel anlamda bir işlem var ve bu işlem belirli bir zamanda gerçekleşecek ise ve biz bu işlem gerçekleştiğinde gibi anları yaklamak istiyorsak bu noktada Promise’lere başvurabiliriz.
Oluşturduğumuz promise’leri kullanırken ise Chaining yapısına başvururuz ve .then()
ve .catch()
kullanarak ardışıl işlemleri birbirine bağlayabiliriz.
Senaryo
Bir Promise nesnesi oluşturmak, nesneyi kullanmak ve Promise’i nasıl yöneteceğinizi öğrenmek için bir örnek uygulama yapalım.
Örneğin, biraz zaman alan bir veri çekme işlemimiz olsun. Bu veri çekme işlemini bir fonksiyonda yaptığımızı düşünelim. Normalde fonksiyondan gelen veriyi bir alt satırda kullanırız ama bu veriyi asenkron olarak çekmek istiyorsak bir alt satırda veri henüz gelmemiş olabilir çünkü asenkron programlama o satırın bitmesini beklemeden işlem yapmaya devam edecektir. Öyleyse bu fonksiyon içindeki işlemin tamamlanıp tamamlanmaması önem arz etmektedir ve yine bu fonksiyonun “tamamlandığında” anını ve başarılı sonuç veya hatalı sonuç durumlarını yakalayabilmem gerekmektedir.
İşte Promise bu ihtiyaçlar için biçilmiş kaftandır.
Promise Yapısı
Örneğimizi nasıl kodlayabilirdik düşünelim ve promise yapısı senaryomuzdan hareketle biraz anlamlandırmaya çalışalım.
Bu işlem başarılı döndüğünde kısmını nasıl yakayalayabilirim?
Promise chaining mekanizması ile, yani yazdığınız promise’den sonra “.then()” diyerek içerisinde bir callback function ile dönen değeri alabilirsiniz.
Bu işlemin başarısız sonuçlandığı durumu nasıl yakalayabilirim?
Yine promise chaining mekanizması ile, yani yazdığınız promise’den sonra “.catch()” diyerek yine içerisinde bir callback function ile dönen değeri alabilirsiniz.
Peki, promise yazarken başarılı-başarısız durumu nasıl geri döndürebilirim?
resolve
ve reject
bir JavaScript Promise’i oluşturduğunuzda kullanabileceğiniz özel fonksiyonlardır ve bu fonksiyonlar Promise’in içerdiği çalıştırılabilir işlev (executor function) içinde bulunurlar. Bu executer function’lar ile işlem sonucunu dönebilirsiniz.
Uygulama
Promise oluştralım.
new Promise((resolve, reject) => {})
Bekleme işlemini simüle etmek için setTimeout
kullanalım ve 2 saniye bekleyelim. Sonrasında başarılı veya başarısız durumu simüle etmek için rasgele bir değer üretelim ve bu değeri bir koşulla resolve
veya reject
ile döndürelim. Böylece bu Promise
‘i bir işlevin içinde döndürebiliriz ki, bu işlev üzerinden zincirleme (chaining) mekanizmasını kullanabilelim.
function myFetch(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const random = Math.random();
if (random < 0.7) {
// Başarılı sonuç
resolve({ data: `Veri alındı: ${url}` });
} else {
// Hata durumu
reject(new Error(`Veri alınamadı: ${url}`));
}
}, 2000);
});
}
Bu Promise’i şöyle kullanabiliriz:
// Örnek kullanım:
const url = '';
myFetch(url)
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error(error.message);
});
Bu kullanımdan sonra, eğer daha önce fetch
gibi bir fonksiyon kullandıysanız veya axios ile veri çekme kodu gördüyseniz, bu size yabancı gelmeyecektir.
Örnek bir axios ile veri çekme kodu:
axios
.get("https://reqres.in/api/users?page=1")
.then((resp) => {
let id = resp.data.data[4].id;
console.log(id);
})
.catch((err) => {
console.log(err);
});
Bu aşamadan sonra modern JavaScript ile gelen async
ve await
anahtar kelimelerini öğrenebilir ve Promise tabanlı asenkron kodları daha okunabilir hale getirebilirsiniz.
Özetle promise yapıları javascript ile veri çekme gibi vakit alan işlemlerde uygulamanın bloklanmasını engellemek veya kullanıcıya geri bildirimde bulunmak gibi ihtiyaçları karşılayan asenkron işlemleri yapabilmek ve bu işlemler üzerinde kontrolü sağlamak için kullanılan yapılardır.
Konu hakkında daha fazla bilgi edinmek için mozillanın ve w3schools‘un kaynaklarına başvurabilirsiniz.