Bu ders için video bulunmamaktadır.

Bu derse başlamak veya ilerlemenizi kaydetmek için lütfen giriş yapın veya kayıt olun.

Ders İçeriği

Giriş: Callback Cehenneminden Kaçış

Önceki derste, eşzamansız işlemleri yönetmek için callback fonksiyonlarını öğrendik. Callback'ler basit senaryolarda işe yarasa da, birbirine bağlı birden fazla eşzamansız işlem gerçekleştirmemiz gerektiğinde kod hızla karmaşıklaşabilir ve okunması zorlaşabilir. İç içe geçmiş çok sayıda callback fonksiyonu, "Callback Hell" (Callback Cehennemi) veya "Pyramid of Doom" (Kıyamet Piramidi) olarak adlandırılan bir yapıya yol açabilir.

callback_hell.js
// Callback Cehennemi Örneği (Sadece Gösterim Amaçlı)
/*
islem1(function(sonuc1) {
    islem2(sonuc1, function(sonuc2) {
        islem3(sonuc2, function(sonuc3) {
            islem4(sonuc3, function(sonuc4) {
                // ... ve bu böyle devam eder
                console.log("Tüm işlemler bitti!");
            }, function(hata4) { ... });
        }, function(hata3) { ... });
    }, function(hata2) { ... });
}, function(hata1) { ... });
*/

Bu tür kodları okumak, anlamak ve hata ayıklamak oldukça zordur. İşte bu noktada, ES6 (ECMAScript 2015) ile tanıtılan Promise (Söz) nesneleri devreye girer. Promise'lar, eşzamansız işlemleri ve onların sonuçlarını (başarı veya hata) temsil eden nesnelerdir ve callback cehennemine daha temiz, okunabilir ve yönetilebilir bir alternatif sunarlar.

Bu derste, Promise'ların ne olduğunu, nasıl çalıştıklarını, nasıl oluşturulduklarını ve eşzamansız kodumuzu nasıl iyileştirdiklerini öğreneceğiz.

Promise Nedir?

Bir Promise, henüz tamamlanmamış ancak gelecekte bir sonuç (değer veya hata) üretecek olan bir eşzamansız işlemin sonucunu temsil eden bir nesnedir. Bir nevi, "Sana söz veriyorum, bu işlem bittiğinde sana ya sonucu ya da neden başarısız olduğunu bildireceğim" diyen bir yapıdır.

Bir Promise şu üç durumdan birinde olabilir:

  1. Pending (Beklemede): Promise'ın başlangıç durumu. İşlem henüz tamamlanmamıştır (ne başarılı ne de başarısız).
  2. Fulfilled (Başarılı/Yerine Getirildi): İşlem başarıyla tamamlandı. Promise bir değere sahiptir (bu değere resolve edilen değer denir).
  3. Rejected (Başarısız/Reddedildi): İşlem bir hata nedeniyle başarısız oldu. Promise bir hataya (sebebe) sahiptir (bu hataya reject edilen sebep denir).

Bir Promise, pending durumundan ya fulfilled ya da rejected durumuna geçer ve bu geçiş sadece bir kez olur. Bir Promise bir kez sonuçlandığında (settled olduğunda - yani fulfilled veya rejected olduğunda), durumu ve sonucu asla değişmez.

Promise Oluşturma

Bir Promise, new Promise(executor) yapıcı (constructor) fonksiyonu ile oluşturulur. executor, iki argüman alan bir fonksiyondur: resolve ve reject.

  • resolve(value): Eşzamansız işlem başarıyla tamamlandığında çağrılır. value, işlemin sonucudur ve Promise'ı fulfilled durumuna geçirir.
  • reject(reason): Eşzamansız işlem sırasında bir hata oluştuğunda çağrılır. reason, hatanın sebebidir (genellikle bir Error nesnesi) ve Promise'ı rejected durumuna geçirir.

executor fonksiyonu, Promise oluşturulduğunda hemen çalıştırılır ve genellikle eşzamansız işlemi başlatır.

promise_olusturma.js
// Basit bir Promise oluşturalım
const benimPromise = new Promise(function(resolve, reject) {
    console.log("Promise executor çalıştı (eşzamansız işlem başlıyor)...");
    
    // Eşzamansız işlemi simüle edelim (örn. setTimeout)
    setTimeout(function() {
        const basarili = Math.random() > 0.3; // %70 başarı ihtimali
        
        if (basarili) {
            const sonuc = "Veri başarıyla alındı!";
            console.log("İşlem başarılı, resolve çağrılıyor...");
            resolve(sonuc); // Promise'ı başarılı (fulfilled) yap ve sonucu ilet
        } else {
            const hata = new Error("Veri alınırken hata oluştu!");
            console.error("İşlem başarısız, reject çağrılıyor...");
            reject(hata); // Promise'ı başarısız (rejected) yap ve hatayı ilet
        }
    }, 2000); // 2 saniye sonra
});

console.log("Promise oluşturuldu (durum: pending).", benimPromise);

// Promise'ın sonucunu nasıl kullanacağımızı birazdan göreceğiz.

Not: Genellikle kendi Promise'larımızı doğrudan oluşturmak yerine, fetch gibi yerleşik API'lerin veya kütüphanelerin döndürdüğü Promise'ları kullanırız. Ancak Promise'ın nasıl çalıştığını anlamak için nasıl oluşturulduğunu bilmek önemlidir.

Promise Kullanımı: .then().catch().finally()

Bir Promise oluşturulduktan veya bir fonksiyondan alındıktan sonra, onun sonucunu (başarı veya hata) işlemek için özel metodlar kullanılır:

promise.then(onFulfilled, onRejected)

Bir Promise'ın sonucunu (settled olduğunda) işlemek için en temel metoddur. İki adet opsiyonel callback fonksiyonu alır:

  • onFulfilled(value): Promise başarılı (fulfilled) olduğunda çalıştırılır. Argüman olarak Promise'ın resolve edilen değerini alır.
  • onRejected(reason): Promise başarısız (rejected) olduğunda çalıştırılır. Argüman olarak Promise'ın reject edilen sebebini (hata nesnesini) alır.

.then() metodu yeni bir Promise döndürür. Bu, Promise'ları zincirlememizi (chaining) sağlar.

then_kullanimi.js
// Önceki örnekteki benimPromise'ı kullanalım

console.log("Promise sonucunu bekliyorum...");

benimPromise.then(
    // 1. argüman: onFulfilled callback'i
    function(sonuc) {
        console.log("Promise Başarılı Oldu! Sonuç:", sonuc);
    },
    // 2. argüman: onRejected callback'i
    function(hata) {
        console.error("Promise Başarısız Oldu! Sebep:", hata.message);
    }
);

console.log(".then() çağrıldı, callback'lerin çalışması bekleniyor...");

// Çıktı (Başarılı senaryo):
// Promise oluşturuldu (durum: pending). Promise {}
// Promise sonucunu bekliyorum...
// .then() çağrıldı, callback'lerin çalışması bekleniyor...
// Promise executor çalıştı (eşzamansız işlem başlıyor)...
// (2 saniye sonra)
// İşlem başarılı, resolve çağrılıyor...
// Promise Başarılı Oldu! Sonuç: Veri başarıyla alındı!

// Çıktı (Başarısız senaryo):
// ... (başlangıç aynı)
// (2 saniye sonra)
// İşlem başarısız, reject çağrılıyor...
// Promise Başarısız Oldu! Sebep: Veri alınırken hata oluştu!

promise.catch(onRejected)

Sadece hataları (rejected durumunu) yakalamak için kullanılan bir kısayoldur. promise.then(null, onRejected) ile eşdeğerdir.

Kodun okunabilirliğini artırır, çünkü başarı ve hata işleme mantığını ayırır.

catch_kullanimi.js
const baskaPromise = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject(new Error("Bir şeyler ters gitti!"));
    }, 1000);
});

baskaPromise
    .then(function(sonuc) {
        // Bu kısım hiç çalışmayacak çünkü Promise reject edilecek
        console.log("Başarılı:", sonuc);
    })
    .catch(function(hata) {
        // Sadece hata durumunda çalışır
        console.error("Catch ile yakalandı:", hata.message);
    });

// Çıktı:
// (1 saniye sonra)
// Catch ile yakalandı: Bir şeyler ters gitti!

promise.finally(onFinally)

Promise'ın sonucundan (başarılı veya başarısız) bağımsız olarak, Promise sonuçlandığında (settled olduğunda) her zaman çalıştırılacak bir callback fonksiyonu eklemek için kullanılır.

Genellikle temizleme işlemleri (örneğin, yükleniyor göstergesini kaldırmak, dosyayı kapatmak) için kullanılır.

onFinally callback'i herhangi bir argüman almaz.

finally_kullanimi.js
function islemYap() {
    console.log("İşlem başlıyor...");
    // Yükleniyor göstergesini göster (varsayım)
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (Math.random() > 0.5) {
                resolve("İşlem tamamlandı.");
            } else {
                reject(new Error("İşlem başarısız."));
            }
        }, 1500);
    });
}

islemYap()
    .then(sonuc => {
        console.log("Sonuç:", sonuc);
    })
    .catch(hata => {
        console.error("Hata:", hata.message);
    })
    .finally(() => {
        // Bu blok her zaman çalışır (başarı veya hata fark etmez)
        console.log("İşlem bitti (finally bloğu).");
        // Yükleniyor göstergesini kaldır (varsayım)
    });

Promise Zincirleme (Chaining)

Promise'ların en güçlü özelliklerinden biri zincirlenebilmeleridir. .then().catch() ve .finally() metodları her zaman yeni bir Promise döndürdüğü için, bu metodları ardışık olarak çağırarak bir dizi eşzamansız işlemi sırayla ve daha okunabilir bir şekilde yönetebiliriz.

Bir .then() içindeki callback fonksiyonu:

  • Bir değer döndürürse, zincirdeki bir sonraki .then() bu değeri alır.
  • Yeni bir Promise döndürürse, zincirdeki bir sonraki .then() bu yeni Promise'ın çözülmesini (resolve olmasını) bekler ve onun sonucunu alır.
  • Bir hata fırlatırsa (veya reject edilmiş bir Promise döndürürse), zincirdeki bir sonraki .catch() bloğu çalıştırılır.
promise_chaining.js
// Adım 1: Kullanıcı ID'sini al (Promise döndürür)
function kullaniciIdAl() {
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log("Adım 1: Kullanıcı ID alındı.");
            resolve(123);
        }, 500);
    });
}

// Adım 2: Kullanıcı ID'si ile kullanıcı bilgilerini al (Promise döndürür)
function kullaniciBilgisiAl(id) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (id === 123) {
                console.log("Adım 2: Kullanıcı bilgisi alındı.");
                resolve({ id: id, ad: "Ahmet" });
            } else {
                reject(new Error("Kullanıcı bulunamadı!"));
            }
        }, 1000);
    });
}

// Adım 3: Kullanıcı bilgileriyle kullanıcının gönderilerini al (Promise döndürür)
function kullaniciGonderileriniAl(kullanici) {
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log(`Adım 3: ${kullanici.ad} kullanıcısının gönderileri alınıyor...`);
            resolve(["Gönderi 1", "Gönderi 2"]);
        }, 800);
    });
}

// Promise Zinciri
kullaniciIdAl() // Adım 1'i başlat
    .then(id => {
        // Adım 1 başarılıysa, Adım 2'yi başlat ve onun Promise'ını döndür
        console.log("Alınan ID:", id);
        return kullaniciBilgisiAl(id);
    })
    .then(kullanici => {
        // Adım 2 başarılıysa, Adım 3'ü başlat ve onun Promise'ını döndür
        console.log("Alınan Kullanıcı:", kullanici);
        return kullaniciGonderileriniAl(kullanici);
    })
    .then(gonderiler => {
        // Adım 3 başarılıysa, son sonucu işle
        console.log("Alınan Gönderiler:", gonderiler);
        console.log("Tüm adımlar başarıyla tamamlandı!");
    })
    .catch(hata => {
        // Zincirdeki herhangi bir adımda hata olursa bu blok çalışır
        console.error("Zincirde Hata Oluştu:", hata.message);
    })
    .finally(() => {
        console.log("Promise zinciri sona erdi (başarılı veya başarısız).");
    });

console.log("Promise zinciri başlatıldı...");

// Çıktı:
// Promise zinciri başlatıldı...
// (0.5 saniye sonra)
// Adım 1: Kullanıcı ID alındı.
// Alınan ID: 123
// (1 saniye sonra)
// Adım 2: Kullanıcı bilgisi alındı.
// Alınan Kullanıcı: {id: 123, ad: 'Ahmet'}
// (0.8 saniye sonra)
// Adım 3: Ahmet kullanıcısının gönderileri alınıyor...
// Alınan Gönderiler: ['Gönderi 1', 'Gönderi 2']
// Tüm adımlar başarıyla tamamlandı!
// Promise zinciri sona erdi (başarılı veya başarısız).

Gördüğünüz gibi, Promise zincirleme, callback cehennemine göre çok daha düz, okunabilir ve yönetilebilir bir kod yapısı sunar.

Alıştırmalar

  1. 1 saniye sonra "Merhaba" mesajını resolve eden bir Promise oluşturun ve .then() ile bu mesajı konsola yazdırın.
  2. 2 saniye sonra rastgele bir sayı üreten ve bu sayıyı resolve eden bir Promise oluşturun. .then() ile sayıyı alın ve çift mi tek mi olduğunu konsola yazdırın.
  3. 1.5 saniye sonra %50 ihtimalle resolve eden (%50 ihtimalle "Başarılı!" mesajıyla), %50 ihtimalle reject eden (bir Error nesnesiyle) bir Promise oluşturun. Hem .then() hem de .catch() kullanarak her iki durumu da ele alın.
  4. Bir Promise zinciri oluşturun: İlk .then() bir sayıyı alsın ve 2 katını döndürsün. İkinci .then() bu sonucu alsın, 10 eklesin ve sonucu konsola yazdırsın. Zincirin sonuna bir .catch() ekleyerek olası hataları yakalayın.
  5. Promise.resolve(deger) ve Promise.reject(sebep) statik metodlarını araştırın. Bunlar ne işe yarar ve nasıl kullanılır?

Bu derste, eşzamansız JavaScript kodunu yönetmek için callback'lere güçlü bir alternatif olan Promise'ları öğrendik:

  • Promise: Gelecekteki bir işlemin sonucunu (başarı veya hata) temsil eden bir nesnedir.
  • Durumlar: Pending (beklemede), Fulfilled (başarılı), Rejected (başarısız).
  • Oluşturma: new Promise((resolve, reject) => { ... }) ile yapılır. resolve başarıyı, reject hatayı bildirir.
  • Kullanım:
    • .then(onFulfilled, onRejected): Başarı ve/veya hata durumlarını işler.
    • .catch(onRejected): Sadece hataları işler.
    • .finally(onFinally): Sonuçtan bağımsız olarak her zaman çalışır.
  • Zincirleme (Chaining): .then().catch().finally() metodlarının yeni Promise'lar döndürmesi sayesinde, eşzamansız işlemler ardışık ve okunabilir bir şekilde bağlanabilir.
  • Faydaları: Callback cehennemini önler, kod okunabilirliğini ve yönetilebilirliğini artırır, hata yönetimini kolaylaştırır.

Promise'lar, modern JavaScript'te eşzamansız programlamanın temel taşlarından biridir. Bir sonraki derste, Promise'lar üzerine inşa edilmiş ve eşzamansız kodu daha da senkron gibi yazmamızı sağlayan async/await sözdizimini inceleyeceğiz.