İçeriğe geç
Muhammet Şafak
Diller 3 dk okuma

JavaScript'te Promise ve asenkron akış

JavaScript'te Promise nedir, callback yığınından nasıl kurtulunur; then, catch zincirleme ve temel asenkron akış kalıpları.


JavaScript’te asenkron işlemler her zaman biraz tuhaf hissettirmiştir. Dil single-threaded çalışıyor ama AJAX istekleri, zamanlayıcılar ve dosya okuma gibi işlemler bloklamaksızın gerçekleşiyor. Bu durumla başa çıkmak için yıllarca callback kullandık. İşe yarıyor, ama bir noktada okunamaz hale geliyor.

Promise, JavaScript’te ES6 ile dile eklenen, asenkron işlemin gelecekteki sonucunu temsil eden bir nesne. Henüz tamamlanmamış, başarıyla tamamlanmış ya da başarısız olmuş üç durumdan birinde bulunuyor. Bu yapı, callback zincirine karşı daha okunur ve yönetilebilir bir alternatif sunuyor.

Callback cehennemi

Önce sorunun kaynağını görmek için şöyle bir senaryo düşünelim: Kullanıcı verisini çek, sonra bu kullanıcının siparişlerini çek, sonra her siparişin ürünlerini çek.

getUser(userId, function(user) {
    getOrders(user.id, function(orders) {
        getProducts(orders[0].id, function(products) {
            // Bir şeyler yap
        }, function(err) {
            console.error('Ürün hatası:', err);
        });
    }, function(err) {
        console.error('Sipariş hatası:', err);
    });
}, function(err) {
    console.error('Kullanıcı hatası:', err);
});

Her adımda iki callback gerekiyor: biri başarı için biri hata için. Girintiler derinleştikçe okumak güçleşiyor. Buna “callback cehennemi” ya da “piramit of doom” (ölüm piramidi) deniliyor.

Promise ile aynı akış

getUser(userId)
    .then(function(user) {
        return getOrders(user.id);
    })
    .then(function(orders) {
        return getProducts(orders[0].id);
    })
    .then(function(products) {
        // Ürünlerle bir şeyler yap
    })
    .catch(function(err) {
        console.error('Bir hata oluştu:', err);
    });

Tüm hatalar tek bir catch bloğunda yakalanıyor. Zincirdeki herhangi bir adımda hata oluşursa doğrudan catch’e düşüyor. Kod soldan sağa, yukarıdan aşağıya okunuyor.

Promise oluşturmak

Kendi fonksiyonunuzu Promise döndürecek şekilde yazmak için new Promise() yapısı kullanılıyor:

function delay(ms) {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            resolve('Bekleme tamamlandı');
        }, ms);
    });
}

delay(1000).then(function(message) {
    console.log(message); // "Bekleme tamamlandı"
});

resolve çağrıldığında Promise başarıya ulaşıyor ve .then() tetikleniyor. reject çağrıldığında veya bir exception fırlatıldığında .catch() tetikleniyor.

Promise.all ile paralel istekler

Birden fazla işlemi aynı anda başlatıp hepsini beklemeniz gerektiğinde Promise.all kullanılıyor:

var userRequest = fetch('/api/user/1');
var settingsRequest = fetch('/api/settings');

Promise.all([userRequest, settingsRequest])
    .then(function(responses) {
        var userResponse = responses[0];
        var settingsResponse = responses[1];
        // Her ikisi de hazır
    })
    .catch(function(err) {
        // Herhangi biri başarısız olursa buraya düşer
    });

Sırayla beklemek yerine ikisi paralel gidiyor; toplam süre en uzun isteğin süresine eşit oluyor.

Promise.all’ın bir kısıtlaması var: dizideki herhangi bir Promise başarısız olursa tüm grup başarısız sayılıyor ve catch’e düşüyor. Bazı işlemlerin başarısız olmasına izin verip diğerlerine devam etmek istiyorsanız Promise.allSettled daha uygun — ama bu ES2020’de geldi; 2017’de elle ele almak gerekiyordu.

Dikkat edilmesi gereken noktar

Promise zincirinde return unutmak yaygın bir hata. .then() içinde yeni bir Promise döndürmezseniz zincirin geri kalanı o Promise’i beklemeden devam eder:

// Hatalı: return yok
.then(function(user) {
    getOrders(user.id); // Bu Promise beklenmeden geçilir
})

// Doğru: return var
.then(function(user) {
    return getOrders(user.id);
})

Bu hatayı yapmak kolay ve debugging sırasında görünmesi güç. Semptom şöyle: orders parametresi then’e undefined olarak geliyor çünkü önceki adım hiçbir şey döndürmedi. Kodu ilk kez okuyanlar genellikle birkaç dakika bunu aramak zorunda kalıyor.

ES6 ok fonksiyonlarıyla

Arrow function ile zincir daha kısa görünüyor:

getUser(userId)
    .then(user => getOrders(user.id))
    .then(orders => getProducts(orders[0].id))
    .then(products => console.log(products))
    .catch(err => console.error(err));

Ok fonksiyonunda tek satırlık bir ifade yazınca return örtük olarak çalışıyor. Bu, zincirdeki return unutma hatasını da azaltıyor; ama çok satırlı blok kullanınca yine return gerekiyor.

Promise’i tam anlamıyla kavramak biraz zaman alıyor; özellikle zincirleme davranışını zihinsel olarak modellemek ilk başta güç hissettiriyor. PHP’de senkron kodu satır satır takip etmek alışılagelmiş bir şey; “bu fonksiyon şu an çalışmıyor, sonra çalışacak” fikrini içselleştirmek bir paradigma geçişi gerektiriyor. Ama bir kez yerleşince callback yığınlarına dönmek istemiyorsunuz.

Promise, bir değer değil bir “değerin geleceğine dair söz”dür. Bunu içselleştirince .then() zincirinin neden her adımda yeni bir Promise döndürdüğü anlaşılıyor. Değer henüz yok; ne zaman hazır olacağını bilmiyorsunuz, ama hazır olduğunda ne yapılacağını önceden tanımlamış oluyorsunuz. Bu “gelecekteki işlemi şimdiden tarif etme” alışkanlığı, bir süre sonra çok daha geniş bir programlama zihniyetinin kapısını açıyor. 2017’de bunu tam kavramak biraz zaman aldı; ama o kavrayış sonrasında JavaScript’le ilişkim değişti.

Etiketler: #JavaScript
Paylaş:

İlgili Yazılar

Sitede Ara

Yazı, proje ve sayfalarda arama yapmak için yazmaya başlayın.

Esc ile kapat Pagefind ile güçlendirildi