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ş: Eşzamansız Kodun Evrimi

Önceki derslerde, JavaScript'te eşzamansız programlamanın temellerini, callback fonksiyonlarını ve Promise'ları öğrendik. Promise'lar, callback cehennemini önleyerek eşzamansız kodu daha okunabilir ve yönetilebilir hale getirdi. Ancak, karmaşık eşzamansız işlemler söz konusu olduğunda, Promise zincirleri bile uzun ve karmaşık olabilir.

ES2017 (ES8) ile tanıtılan async/await sözdizimi, Promise'lar üzerine inşa edilmiş ve eşzamansız kodu daha da okunabilir ve yazması kolay hale getiren bir soyutlama katmanıdır. Async/await, eşzamansız kodu neredeyse senkron kod gibi yazmanıza olanak tanır, bu da özellikle karmaşık eşzamansız işlemler için büyük bir avantajdır.

Bu derste, async/await'in ne olduğunu, nasıl çalıştığını, Promise'larla ilişkisini ve eşzamansız kodumuzu nasıl daha da iyileştirdiğini öğreneceğiz.

Async/Await Nedir?

Async/await, Promise'ları daha okunabilir ve yazması kolay bir şekilde kullanmamızı sağlayan bir JavaScript sözdizimi özelliğidir. İki ana bileşenden oluşur:

  1. async anahtar kelimesi: Bir fonksiyonun önüne eklenir ve o fonksiyonun her zaman bir Promise döndüreceğini belirtir. Fonksiyon bir değer döndürürse, JavaScript bu değeri otomatik olarak bir Promise içine sarar (Promise.resolve ile).
  2. await anahtar kelimesi: Sadece async fonksiyonlar içinde kullanılabilir ve bir Promise'ın çözülmesini (resolve edilmesini) bekler. await ifadesi, Promise çözülene kadar fonksiyonun yürütülmesini "duraklatır" (aslında bloklamadan bekletir) ve çözüldüğünde, Promise'ın sonucunu döndürür.

Async/await, Promise'ların üzerine inşa edilmiştir ve aslında Promise'ları kullanmanın daha zarif bir yoludur. Arka planda, JavaScript motoru hala Promise'ları kullanır, ancak kod yazma ve okuma deneyimi çok daha senkron kod benzeri olur.

Async Fonksiyonlar

Bir fonksiyonu async olarak tanımlamak için, fonksiyon tanımının önüne async anahtar kelimesini eklemeniz yeterlidir. Bu, fonksiyonun her zaman bir Promise döndüreceği anlamına gelir.

async_fonksiyon.js
// Normal fonksiyon
function normalFonksiyon() {
    return "Normal fonksiyondan merhaba";
}

// Async fonksiyon
async function asyncFonksiyon() {
    return "Async fonksiyondan merhaba"; // Otomatik olarak Promise.resolve() ile sarılır
}

// Async ok (arrow) fonksiyonu
const asyncOkFonksiyonu = async () => {
    return "Async ok fonksiyonundan merhaba";
};

// Kullanım
console.log(normalFonksiyon()); // "Normal fonksiyondan merhaba"
console.log(asyncFonksiyon()); // Promise {: "Async fonksiyondan merhaba"}

// Promise'ı kullanmak için .then() gerekir
asyncFonksiyon().then(sonuc => {
    console.log(sonuc); // "Async fonksiyondan merhaba"
});

Gördüğünüz gibi, async fonksiyon bir değer döndürdüğünde, JavaScript bu değeri otomatik olarak bir Promise içine sarar. Eğer fonksiyon bir hata fırlatırsa, döndürülen Promise otomatik olarak reject edilir.

async_hata.js
async function basariliIslem() {
    return "İşlem başarılı";
}

async function hataliIslem() {
    throw new Error("Bir hata oluştu!");
    // Bu, aşağıdakine eşdeğerdir:
    // return Promise.reject(new Error("Bir hata oluştu!"));
}

basariliIslem()
    .then(sonuc => console.log("Başarılı:", sonuc))
    .catch(hata => console.error("Hata:", hata.message));

hataliIslem()
    .then(sonuc => console.log("Başarılı:", sonuc))
    .catch(hata => console.error("Hata:", hata.message));

// Çıktı:
// Başarılı: İşlem başarılı
// Hata: Bir hata oluştu!

Await Operatörü

await operatörü, bir Promise'ın çözülmesini (resolve edilmesini) beklemek için kullanılır ve sadece async fonksiyonlar içinde kullanılabilir. await ifadesi, Promise çözülene kadar fonksiyonun yürütülmesini "duraklatır" ve çözüldüğünde, Promise'ın sonucunu döndürür.

await_kullanimi.js
// Promise döndüren bir fonksiyon
function bekle(ms) {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(`${ms} milisaniye beklendi`);
        }, ms);
    });
}

// Async fonksiyon içinde await kullanımı
async function islemYap() {
    console.log("İşlem başlıyor...");
    
    // await, Promise çözülene kadar bekler ve sonucu döndürür
    const sonuc1 = await bekle(1000); // 1 saniye bekle
    console.log(sonuc1);
    
    const sonuc2 = await bekle(2000); // 2 saniye bekle
    console.log(sonuc2);
    
    console.log("İşlem tamamlandı.");
    return "Tüm işlemler bitti";
}

// Async fonksiyonu çağırma
console.log("Ana program başladı.");
islemYap().then(sonuc => {
    console.log("Ana program sonucu:", sonuc);
});
console.log("Ana program devam ediyor...");

// Çıktı:
// Ana program başladı.
// İşlem başlıyor...
// Ana program devam ediyor...
// (1 saniye sonra)
// 1000 milisaniye beklendi
// (2 saniye sonra)
// 2000 milisaniye beklendi
// İşlem tamamlandı.
// Ana program sonucu: Tüm işlemler bitti

Bu örnekte, islemYap fonksiyonu içinde await kullanarak, Promise'ların çözülmesini sırayla bekliyoruz. Kod, sanki senkron çalışıyormuş gibi görünüyor, ancak aslında eşzamansız olarak çalışıyor ve ana iş parçacığını bloklamıyor.

Not: await ifadesi, Promise çözülene kadar sadece async fonksiyonun içindeki yürütmeyi duraklatır. Ana program veya diğer fonksiyonlar çalışmaya devam eder. Bu, yukarıdaki örnekte "Ana program devam ediyor..." mesajının, islemYap fonksiyonu içindeki beklemelerden etkilenmeden hemen yazdırılmasından anlaşılabilir.

Promise Zincirleme vs. Async/Await

Aynı eşzamansız işlemi hem Promise zincirleme hem de async/await kullanarak nasıl yazabileceğimizi karşılaştıralım:

promise_vs_async_await.js
// Eşzamansız işlemler için örnek fonksiyonlar
function kullaniciIdAl() {
    return new Promise(resolve => {
        setTimeout(() => resolve(123), 500);
    });
}

function kullaniciBilgisiAl(id) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (id === 123) {
                resolve({ id: id, ad: "Ahmet" });
            } else {
                reject(new Error("Kullanıcı bulunamadı!"));
            }
        }, 1000);
    });
}

function kullaniciGonderileriniAl(kullanici) {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(["Gönderi 1", "Gönderi 2"]);
        }, 800);
    });
}

// Promise Zincirleme ile
function promiseZinciriyleIslemYap() {
    console.log("Promise zinciri başlatılıyor...");
    
    return kullaniciIdAl()
        .then(id => {
            console.log("ID alındı:", id);
            return kullaniciBilgisiAl(id);
        })
        .then(kullanici => {
            console.log("Kullanıcı bilgisi alındı:", kullanici);
            return kullaniciGonderileriniAl(kullanici);
        })
        .then(gonderiler => {
            console.log("Gönderiler alındı:", gonderiler);
            return "Promise zinciri tamamlandı";
        })
        .catch(hata => {
            console.error("Hata oluştu:", hata.message);
            throw hata; // Hatayı yeniden fırlat
        });
}

// Async/Await ile
async function asyncAwaitIleIslemYap() {
    console.log("Async/await işlemi başlatılıyor...");
    
    try {
        const id = await kullaniciIdAl();
        console.log("ID alındı:", id);
        
        const kullanici = await kullaniciBilgisiAl(id);
        console.log("Kullanıcı bilgisi alındı:", kullanici);
        
        const gonderiler = await kullaniciGonderileriniAl(kullanici);
        console.log("Gönderiler alındı:", gonderiler);
        
        return "Async/await işlemi tamamlandı";
    } catch (hata) {
        console.error("Hata oluştu:", hata.message);
        throw hata; // Hatayı yeniden fırlat
    }
}

// Her iki yaklaşımı da çağıralım
promiseZinciriyleIslemYap()
    .then(sonuc => console.log(sonuc))
    .catch(hata => console.error("Ana program hatayı yakaladı:", hata.message));

asyncAwaitIleIslemYap()
    .then(sonuc => console.log(sonuc))
    .catch(hata => console.error("Ana program hatayı yakaladı:", hata.message));

İki yaklaşımı karşılaştırdığımızda, async/await'in daha temiz, daha okunabilir ve daha az iç içe geçmiş bir kod yapısı sunduğunu görebiliriz. Özellikle, try/catch bloğu kullanarak hata yönetimi daha doğal ve senkron kod benzeri hale gelir.

Hata Yönetimi

Async/await ile hata yönetimi, normal JavaScript'teki try/catch bloklarını kullanarak yapılır. Bu, Promise'lardaki .catch() metoduna göre daha doğal ve anlaşılır bir hata yönetimi sağlar.

async_await_hata_yonetimi.js
async function veriGetir(id) {
    try {
        // Eşzamansız bir işlem simülasyonu
        const sonuc = await new Promise((resolve, reject) => {
            setTimeout(() => {
                if (id <= 0) {
                    reject(new Error("Geçersiz ID!"));
                } else if (id > 100) {
                    reject(new Error("ID bulunamadı!"));
                } else {
                    resolve(`ID ${id} için veri`);
                }
            }, 1000);
        });
        
        console.log("Veri başarıyla alındı:", sonuc);
        return sonuc;
    } catch (hata) {
        console.error("Veri getirme hatası:", hata.message);
        // Hatayı işle veya yeniden fırlat
        throw new Error(`Veri getirilemedi: ${hata.message}`);
    } finally {
        console.log("Veri getirme işlemi tamamlandı (başarılı veya başarısız).");
    }
}

// Kullanım
async function anaFonksiyon() {
    try {
        const veri1 = await veriGetir(42); // Başarılı olacak
        console.log("Ana fonksiyon veri1:", veri1);
        
        const veri2 = await veriGetir(-5); // Hata fırlatacak
        console.log("Ana fonksiyon veri2:", veri2); // Bu satır çalışmayacak
    } catch (hata) {
        console.error("Ana fonksiyon hatayı yakaladı:", hata.message);
    }
    
    console.log("Ana fonksiyon devam ediyor...");
    
    try {
        const veri3 = await veriGetir(200); // Başka bir hata
    } catch (hata) {
        console.error("İkinci hata yakalandı:", hata.message);
    }
    
    console.log("Ana fonksiyon tamamlandı.");
}

anaFonksiyon();

Bu örnekte, veriGetir fonksiyonu içinde try/catch bloğu kullanarak eşzamansız işlem sırasında oluşabilecek hataları yakalıyoruz. Ayrıca, anaFonksiyon içinde de try/catch blokları kullanarak, veriGetir fonksiyonundan fırlatılan hataları yakalıyoruz.

Async/await ile hata yönetimi, Promise'lardaki .catch() metoduna göre daha doğal ve anlaşılır bir yapı sunar, çünkü normal JavaScript'teki try/catch bloklarını kullanır.

Paralel İşlemler

Async/await, varsayılan olarak eşzamansız işlemleri sırayla (sequential) çalıştırır. Ancak bazen, birbirinden bağımsız birden fazla eşzamansız işlemi paralel olarak çalıştırmak daha verimli olabilir.

Paralel işlemler için Promise.all()Promise.race()Promise.allSettled() ve Promise.any() gibi Promise metodlarını async/await ile birlikte kullanabiliriz.

Promise.all() ile Paralel İşlemler

Promise.all(), bir dizi Promise'ı paralel olarak çalıştırır ve tüm Promise'lar başarılı olduğunda çözülen (veya herhangi biri başarısız olduğunda reddedilen) yeni bir Promise döndürür.

promise_all.js
async function parallelIslemler() {
    console.log("Paralel işlemler başlatılıyor...");
    
    try {
        // Üç eşzamansız işlemi paralel olarak başlat
        const islemler = [
            fetch('https://jsonplaceholder.typicode.com/users/1'),
            fetch('https://jsonplaceholder.typicode.com/posts/1'),
            fetch('https://jsonplaceholder.typicode.com/todos/1')
        ];
        
        // Promise.all ile tüm işlemlerin tamamlanmasını bekle
        const [kullaniciYanit, gonderiYanit, gorevYanit] = await Promise.all(islemler);
        
        // Yanıtları JSON'a dönüştür
        const kullanici = await kullaniciYanit.json();
        const gonderi = await gonderiYanit.json();
        const gorev = await gorevYanit.json();
        
        console.log("Tüm veriler paralel olarak alındı:");
        console.log("Kullanıcı:", kullanici.name);
        console.log("Gönderi:", gonderi.title);
        console.log("Görev:", gorev.title);
        
        return { kullanici, gonderi, gorev };
    } catch (hata) {
        console.error("Paralel işlemlerde hata:", hata.message);
        throw hata;
    }
}

// Kullanım
parallelIslemler()
    .then(sonuc => console.log("İşlem tamamlandı:", sonuc))
    .catch(hata => console.error("Ana program hatayı yakaladı:", hata.message));

Bu örnekte, üç farklı API isteğini paralel olarak başlatıyoruz ve Promise.all() ile tüm yanıtların gelmesini bekliyoruz. Bu, işlemleri sırayla çalıştırmaktan çok daha hızlıdır.

Diğer Promise Kombinasyon Metodları

  • Promise.race(): Verilen Promise'lardan herhangi biri çözüldüğünde (veya reddedildiğinde) çözülen yeni bir Promise döndürür. İlk tamamlanan işlemin sonucunu alır.
  • Promise.allSettled(): Tüm Promise'lar tamamlandığında (başarılı veya başarısız) çözülen yeni bir Promise döndürür. Her Promise'ın durumunu ve sonucunu içeren bir dizi döndürür.
  • Promise.any(): Verilen Promise'lardan herhangi biri başarıyla çözüldüğünde çözülen yeni bir Promise döndürür. Tüm Promise'lar reddedilirse, AggregateError ile reddedilir.
promise_kombinasyonlari.js
// Farklı sürelerde tamamlanan Promise'lar oluşturalım
function islem(id, ms, basarili = true) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (basarili) {
                resolve(`İşlem ${id}: ${ms}ms sürdü`);
            } else {
                reject(new Error(`İşlem ${id} başarısız oldu!`));
            }
        }, ms);
    });
}

async function promiseKombinasyonlari() {
    try {
        // Promise.race - İlk tamamlanan işlemi alır
        const racePromises = [
            islem(1, 1000),
            islem(2, 500),  // Bu en hızlı tamamlanacak
            islem(3, 1500)
        ];
        const raceResult = await Promise.race(racePromises);
        console.log("Promise.race sonucu:", raceResult); // İşlem 2: 500ms sürdü
        
        // Promise.allSettled - Tüm işlemlerin durumunu alır
        const allSettledPromises = [
            islem(4, 800),
            islem(5, 1200, false),  // Bu başarısız olacak
            islem(6, 600)
        ];
        const allSettledResult = await Promise.allSettled(allSettledPromises);
        console.log("Promise.allSettled sonucu:", allSettledResult);
        // [
        //   {status: "fulfilled", value: "İşlem 4: 800ms sürdü"},
        //   {status: "rejected", reason: Error: İşlem 5 başarısız oldu!},
        //   {status: "fulfilled", value: "İşlem 6: 600ms sürdü"}
        // ]
        
        // Promise.any - İlk başarılı olan işlemi alır
        const anyPromises = [
            islem(7, 1000, false),  // Bu başarısız olacak
            islem(8, 800),          // Bu ilk başarılı olan
            islem(9, 600, false)    // Bu da başarısız olacak
        ];
        const anyResult = await Promise.any(anyPromises);
        console.log("Promise.any sonucu:", anyResult); // İşlem 8: 800ms sürdü
        
    } catch (hata) {
        console.error("Hata:", hata);
    }
}

promiseKombinasyonlari();

Alıştırmalar

  1. Bir sayı alan ve bu sayının karesini 1 saniye sonra döndüren bir async fonksiyon yazın. Fonksiyonu çağırın ve sonucu konsola yazdırın.
  2. Bir dizi ID alan ve her ID için https://jsonplaceholder.typicode.com/posts/{id} adresinden veri çeken bir async fonksiyon yazın. Tüm verileri bir dizide toplayıp döndürün.
  3. İki farklı API'den veri çeken iki ayrı async fonksiyon yazın. Sonra, bu iki fonksiyonu paralel olarak çalıştıran ve her iki sonucu da birleştiren bir üçüncü async fonksiyon yazın.
  4. Bir async fonksiyon içinde, belirli bir olasılıkla hata fırlatan bir Promise oluşturun. Try/catch bloğu kullanarak hatayı yakalayın ve uygun bir mesaj gösterin.
  5. Üç farklı API'den veri çeken üç ayrı async fonksiyon yazın. Promise.race() kullanarak, en hızlı yanıt veren API'nin sonucunu döndüren bir fonksiyon yazın.

Bu derste, eşzamansız JavaScript kodunu daha okunabilir ve yönetilebilir hale getiren async/await sözdizimini öğrendik:

  • Async/Await: Promise'lar üzerine inşa edilmiş, eşzamansız kodu senkron kod gibi yazmamızı sağlayan bir JavaScript sözdizimi özelliğidir.
  • Async Fonksiyonlar: async anahtar kelimesi ile tanımlanır ve her zaman bir Promise döndürür. Fonksiyon bir değer döndürürse, bu değer otomatik olarak bir Promise içine sarılır.
  • Await Operatörü: await anahtar kelimesi, bir Promise'ın çözülmesini beklemek için kullanılır ve sadece async fonksiyonlar içinde kullanılabilir. Promise çözülene kadar fonksiyonun yürütülmesini "duraklatır" ve çözüldüğünde, Promise'ın sonucunu döndürür.
  • Hata Yönetimi: Async/await ile hata yönetimi, normal JavaScript'teki try/catch bloklarını kullanarak yapılır, bu da Promise'lardaki .catch() metoduna göre daha doğal ve anlaşılır bir hata yönetimi sağlar.
  • Paralel İşlemler: Promise.all()Promise.race()Promise.allSettled() ve Promise.any() gibi Promise metodlarını async/await ile birlikte kullanarak, birden fazla eşzamansız işlemi paralel olarak çalıştırabiliriz.

Async/await, modern JavaScript'te eşzamansız programlamanın en yaygın ve önerilen yöntemidir. Promise'lar üzerine inşa edilmiş olsa da, çok daha okunabilir, anlaşılır ve hata yönetimi daha kolay bir kod yapısı sunar.

Bir sonraki derste, modern web uygulamalarında veri alışverişi için kullanılan Fetch API ve AJAX konularını inceleyeceğiz.