6 Aralık 2016 Salı

Javascript'de Promise

Merhaba Webkolog Takipçileri!

JavaScript ile web uygulamaları geliştirirken kaçınılmaz olarak asenkron işlemlerle karşılaşırsınız. Bir API'den veri çekmek, bir dosyayı yüklemek veya belirli bir süre beklemek gibi işlemler, anında sonuç vermez. İşte tam da bu noktada, asenkron kod yönetimini bir kâbus olmaktan çıkarıp bir nimete dönüştüren Promise'ler devreye giriyor! ES6 (ECMAScript 2015) ile hayatımıza giren Promise'ler, "Callback Hell" olarak bilinen iç içe geçmiş fonksiyon yığınlarından kurtulmamızı ve daha okunabilir, yönetilebilir bir kod yazmamızı sağladı. Bugün, Promise'lerin ne olduğunu, neden bu kadar önemli olduğunu ve günlük kodlamalarımızda bize nasıl yardımcı olabileceğini ayrıntılarıyla inceleyeceğiz. Hazırsanız, asenkron JavaScript'in kalbine inelim!


Promise Nedir?

Bir Promise (Söz), JavaScript'te henüz tamamlanmamış ancak gelecekte bir noktada sonuçlanacak asenkron bir işlemin nihai sonucunu temsil eden bir nesnedir. Bir nevi, "sana şimdi bir söz veriyorum, bu işlem ya başarılı olacak ve sana bir değer vereceğim ya da başarısız olacak ve sana bir hata döndüreceğim" demektir.

Bir Promise'in üç olası durumu (state) vardır:

  1. Pending (Beklemede): Başlangıç durumudur. Ne yerine getirildi ne de reddedildi. Asenkron işlem hala devam etmektedir.
  2. Fulfilled (Yerine getirildi / Başarılı): İşlem başarıyla tamamlandı ve Promise bir değer döndürdü.
  3. Rejected (Reddedildi / Başarısız): İşlem bir hata nedeniyle başarısız oldu ve Promise bir hata nedeni döndürdü.

Bir Promise bir kez yerine getirildiğinde veya reddedildiğinde, durumu kalıcı hale gelir ve bir daha değişmez.


Neden Promise Kullanılır?

Promise'ler, özellikle karmaşık asenkron iş akışlarını yönetmek için tasarlanmıştır ve bize önemli avantajlar sunar:

  • Callback Hell'i Önler: İç içe geçmiş (nested) callback fonksiyonlarının neden olduğu okunaksız ve yönetilmesi zor "Callback Hell" yapısını ortadan kaldırır. Promise'ler sayesinde kodumuzu zincirleme (chaining) yöntemiyle daha düz bir yapıda yazabiliriz.
  • Merkezi Hata Yönetimi: Promise zincirinin herhangi bir noktasında meydana gelen hataları tek bir .catch() bloğu ile merkezi olarak yakalama imkanı sunar. Bu, hata yönetimini çok daha kolay ve düzenli hale getirir.
  • Okunabilir ve Yönetilebilir Kod: Asenkron kodu, senkron kod yazıyormuş gibi daha doğrusal ve mantıksal bir akışla yazmamızı sağlar. Özellikle async/await sözdizimi ile bu durum daha da belirginleşir.

Promise Oluşturma ve Kullanımı

1. Promise Oluşturma Temelleri

Bir Promise, new Promise() constructor'ı ile oluşturulur. Bu constructor, iki argüman alan bir fonksiyon alır: resolve (başarılı durum) ve reject (hata durumu). Bu fonksiyonlar Promise'in durumunu değiştirmek için kullanılır.


const myPromise = new Promise((resolve, reject) => {
    // Genellikle burada zaman alıcı asenkron bir işlem yapılır.
    const isSuccess = Math.random() > 0.5; // Örnek: Rastgele başarılı veya başarısız olsun

    if (isSuccess) {
        // İşlem başarılıysa resolve çağırılır
        resolve("İşlem başarıyla tamamlandı ve bu değeri döndürdü!"); // Fulfilled durumu
    } else {
        // İşlem başarısız olursa reject çağırılır
        reject("Oops! İşlem başarısız oldu ve bir hata oluştu."); // Rejected durumu
    }
});

// Promise'i kullanma
myPromise
    .then((result) => {
        // Promise Fulfilled (başarılı) olduğunda bu blok çalışır
        console.log("Başarı:", result);
    })
    .catch((error) => {
        // Promise Rejected (başarısız) olduğunda bu blok çalışır
        console.error("Hata:", error);
    });
2. Asenkron İşlem Örneği (setTimeout ile Gecikme)

Promise'ler genellikle bir sunucu isteği veya dosya okuma gibi zaman alan işlemlerde kullanılır. Bir örnek olarak, setTimeout kullanarak bir gecikme fonksiyonu oluşturalım:


const bekle = (milisaniye) => {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(`${milisaniye} milisaniye beklendi.`);
        }, milisaniye);
    });
};

// Kullanımı: 3 saniye bekleyip mesajı konsola yazdır
bekle(3000)
    .then((mesaj) => {
        console.log(mesaj); // "3000 milisaniye beklendi."
    })
    .catch((hata) => {
        console.error("Bekleme sırasında bir hata oluştu:", hata);
    });
3. Hata Yönetimi: .catch()

Promise'lerin en güzel yanlarından biri, merkezi hata yönetimi sunmalarıdır. Bir Promise zincirinde herhangi bir noktada bir reject durumu oluştuğunda, kontrol en yakın .catch() bloğuna atlar. Bu sayede her adımda ayrı ayrı hata kontrolü yapmaktan kurtuluruz.


const veriCek = (url) => {
    return new Promise((resolve, reject) => {
        // Basit bir simülasyon: URL "hata" içeriyorsa başarısız olsun
        if (url.includes("hata")) {
            reject(`'${url}' adresinden veri çekilirken hata oluştu.`);
        } else {
            setTimeout(() => { // Gerçek bir API çağrısı gibi gecikme
                resolve(`'${url}' adresinden veri başarıyla alındı.`);
            }, 1500);
        }
    });
};

veriCek("https://api.webkolog.com/veriler")
    .then((mesaj) => console.log(mesaj)) // Başarılı mesajı yazar
    .catch((hata) => console.error("Genel Hata Yakalandı:", hata)); // Hata olursa burası çalışır

veriCek("https://api.webkolog.com/hata") // Hata içerecek bir URL
    .then((mesaj) => console.log(mesaj))
    .catch((hata) => console.error("Genel Hata Yakalandı:", hata)); // Hata mesajını yazar
4. Zincirleme (Chaining)

Promise'ler, .then() metodu ile birbirine zincirlenebilir. Bir .then() bloğu, kendisinden önceki Promise tamamlandığında çalışır ve kendisi de yeni bir Promise döndürebilir, bu da bir sonraki .then() bloğunun tetiklenmesini sağlar. Bu yapı, asenkron işlemleri adım adım, sıralı bir şekilde yönetmek için idealdir.


function adim1() {
    console.log("Adım 1 başlıyor...");
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log("Adım 1 tamamlandı.");
            resolve("Adım 1'den gelen veri");
        }, 1000);
    });
}

function adim2(oncekiVeri) {
    console.log("Adım 2 başlıyor, Önceki veri:", oncekiVeri);
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const basarili = Math.random() > 0.3; // %70 başarı oranı
            if (basarili) {
                console.log("Adım 2 tamamlandı.");
                resolve("Adım 2'den gelen veri");
            } else {
                reject("Adım 2 başarısız oldu!");
            }
        }, 1500);
    });
}

function adim3(oncekiVeri) {
    console.log("Adım 3 başlıyor, Önceki veri:", oncekiVeri);
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log("Adım 3 tamamlandı.");
            resolve("Tüm adımlar başarıyla tamamlandı!");
        }, 800);
    });
}

// Promise Zincirlemesi
adim1()
    .then(adim2) // Adım 1 tamamlandığında Adım 2'yi çağır
    .then(adim3) // Adım 2 tamamlandığında Adım 3'ü çağır
    .then((sonuc) => {
        console.log("Nihai Sonuç:", sonuc);
    })
    .catch((hata) => {
        // Zincirdeki herhangi bir adımda hata olursa burası çalışır
        console.error("Zincirleme Hata:", hata);
    });
5. Promise.all(): Paralel İşlemler İçin

Birden fazla asenkron işlemi aynı anda başlatıp, hepsi tamamlandığında (veya herhangi biri hata verdiğinde) tepki vermek istediğimizde Promise.all() metodunu kullanırız. Bu metod, bir dizi Promise alır ve tüm Promise'ler başarıyla tamamlandığında sonuçların bir dizisini döndüren tek bir Promise döndürür. Herhangi biri reddedilirse, Promise.all() hemen o hatayı döndürür.


const gorev1 = bekle(2000).then(() => "Görev 1 tamamlandı");
const gorev2 = bekle(1000).then(() => "Görev 2 tamamlandı");
const gorev3 = bekle(2500).then(() => "Görev 3 tamamlandı");

Promise.all([gorev1, gorev2, gorev3])
    .then((sonuclar) => {
        console.log("Tüm görevler başarıyla tamamlandı:");
        console.log(sonuclar); // ["Görev 1 tamamlandı", "Görev 2 tamamlandı", "Görev 3 tamamlandı"]
    })
    .catch((hata) => {
        console.error("Görevlerden biri başarısız oldu:", hata);
    });

fetch() API ve Promise İlişkisi

Modern web geliştirmede sıkça kullandığımız fetch() API'si, ağ üzerinden kaynaklara (örneğin bir API'ye) HTTP istekleri göndermek için kullanılan modern ve doğal olarak Promise tabanlı bir yöntemdir. Temel olarak sunucudan veri almak (GET) veya göndermek (POST, PUT, DELETE) için kullanılır ve Promise'lerin gücünü doğrudan kullanır.

Basit GET İsteği (Veri Çekme) fetch() ile:

// API'den veri çekme örneği
fetch('https://jsonplaceholder.typicode.com/todos/1') // Örnek bir API adresi
    .then(response => {
        // İlk .then() bloğu, HTTP yanıtını (Response objesi) alır.
        // response.ok, yanıtın 2xx başarılı kod aralığında olup olmadığını kontrol eder.
        if (!response.ok) {
            // Eğer yanıt başarılı değilse, bir hata fırlatırız
            throw new Error(`HTTP hatası! Durum: ${response.status}`);
        }
        // Yanıtı JSON formatına çevirir (bu da bir Promise döndürür)
        return response.json();
    })
    .then(data => {
        // İkinci .then() bloğu, JSON'a çevrilmiş veriyi alır
        console.log("API'den gelen veri:", data);
        // data.title veya data.id gibi verilere erişebiliriz
    })
    .catch(error => {
        // Zincirdeki herhangi bir Promise'ten gelen hatayı yakalar
        console.error('Veri çekerken bir hata oluştu:', error);
    });
POST İsteği (Veri Gönderme) fetch() ile:

fetch('https://jsonplaceholder.typicode.com/posts', {
    method: 'POST', // POST metodu
    headers: {
        'Content-Type': 'application/json', // Gönderilen verinin JSON olduğunu belirtiriz
    },
    body: JSON.stringify({ // Gönderilecek veriyi JSON string'e dönüştürürüz
        title: 'Webkolog Makalesi',
        body: 'Promise konusu çok önemli!',
        userId: 1,
    }),
})
    .then(response => {
        if (!response.ok) {
            throw new Error(`HTTP hatası! Durum: ${response.status}`);
        }
        return response.json();
    })
    .then(data => {
        console.log('Başarılı! Sunucudan gelen yanıt:', data);
    })
    .catch(error => {
        console.error('Veri gönderirken bir hata oluştu:', error);
    });

Gördüğünüz gibi, fetch() API'si Promise'ler sayesinde hem temiz hem de anlaşılır bir şekilde asenkron HTTP istekleri yapmamızı sağlar.


Sonuç

JavaScript'te Promise'ler, asenkron programlamanın karmaşıklığını basitleştiren ve kodunuzu daha okunabilir, yönetilebilir ve hata dostu hale getiren güçlü bir yapıdır. Callback Hell'den kurtulmak, merkezi hata yönetimi sağlamak ve asenkron iş akışlarını doğrusal bir şekilde zincirlemek gibi birçok avantaj sunar. Modern JavaScript geliştirmenin temel taşlarından biri olan Promise'leri iyi anlamak ve fetch() gibi Promise tabanlı API'lerle birlikte kullanmak, web uygulamalarınızı daha verimli ve kullanıcı dostu hale getirmenizi sağlayacaktır. async/await ile birlikte Promise'ler, JavaScript'te asenkron kod yazma deneyimini bambaşka bir seviyeye taşıdı ve bu konuları öğrenmek, modern web geliştiricisi için artık bir zorunluluk haline geldi.

Webkolog'u takipte kalın!

Hepinize bol kodlu ve hatasız Promise deneyimleri dilerim!

0 yorum:

Yorum Gönder