# 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ı.

- Yayın: 2017-09-03
- Kategori: Diller
- Etiketler: JavaScript
- Okuma süresi: 3 dk okuma
- Kaynak: https://www.muhammetsafak.com.tr/blog/javascriptte-promise-ve-asenkron-akis/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
JavaScript'te asenkron işlemler her zaman biraz tuhaf hissettirmiştir. Dil single-threaded çalışıyor ama [AJAX istekleri](/blog/jquery-ile-ajax-sayfayi-yenilemeden-veri-gondermek), 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](/blog/es6-ile-tanisma-let-const-ve-ok-fonksiyonlari) 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.

```javascript
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ış

```javascript
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:

```javascript
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:

```javascript
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:

```javascript
// 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:

```javascript
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.
