Takip et

JavaScript Promise’larını Anlamak: Asenkron Programlamanın Temel Taşı

JavaScript Promise’larını Anlamak: Asenkron Programlamanın Temel Taşı JavaScript, web tarayıcılarında etkileşimli kullanıcı deneyimleri sa

JavaScript Promise’larını Anlamak: Asenkron Programlamanın Temel Taşı

JavaScript, web tarayıcılarında etkileşimli kullanıcı deneyimleri sağlamaktan, sunucu tarafında (Node.js ile) karmaşık uygulamalar geliştirmeye kadar geniş bir yelpazede kullanılan güçlü bir programlama dilidir. Ancak JavaScript’in doğası gereği tek iş parçacıklı (single-threaded) olması, uzun süren veya gecikme potansiyeli olan işlemlerin (örneğin, ağ istekleri, dosya okuma/yazma) uygulamanın donmasına yol açabileceği anlamına gelir. İşte bu noktada asenkron programlama devreye girer ve JavaScript Promise’ları, bu asenkron işlemlerin yönetilmesinde modern ve güçlü bir çözüm sunar.

Bu makalede, JavaScript Promise’larının ne olduğunu, neden bu kadar önemli olduklarını, nasıl çalıştıklarını ve asenkron kodunuzu daha okunabilir, yönetilebilir ve hataya dayanıklı hale getirmek için nasıl kullanabileceğinizi derinlemesine inceleyeceğiz. Callback’lerin sınırlılıklarından başlayarak, Promise’ların temel kavramlarına, zincirleme yeteneklerine, gelişmiş API’lerine ve son olarak async/await ile nasıl daha da basitleştirildiklerine değineceğiz.

Asenkron JavaScript’e Giriş

JavaScript’in tek iş parçacıklı yapısı, aynı anda yalnızca bir görevin yürütülebileceği anlamına gelir. Tarayıcı ortamında, bu iş parçacığı aynı zamanda kullanıcı arayüzünü (UI) de işler. Eğer uzun süren bir işlem bu ana iş parçacığını bloke ederse, kullanıcı arayüzü donar ve yanıt vermez hale gelir. Bu durum, kötü bir kullanıcı deneyimine yol açar.

Asenkron programlama, bu tür bloklama işlemlerini ana iş parçacığından ayırarak, diğer görevlerin (örneğin UI güncellemeleri) eş zamanlı olarak yürütülmesine olanak tanır. JavaScript’in bunu başarmak için kullandığı temel mekanizma, “Olay Döngüsü” (Event Loop) adı verilen bir sistemdir. Olay döngüsü, ana iş parçacığı boş olduğunda bekleyen asenkron görevleri sıraya alır ve yürütür.

Geleneksel Asenkron Yaklaşımlar: Callback’ler

Promise’lardan önce, asenkron işlemleri yönetmenin birincil yolu callback fonksiyonlarıydı. Bir callback, asenkron bir işlemin tamamlanmasının ardından çağrılan bir fonksiyondur.

function veriGetir(url, callback) {
  fetch(url)
    .then(response => response.json())
    .then(data => callback(null, data))
    .catch(error => callback(error, null));
}

veriGetir('https://api.example.com/data', (hata, veri) => {
  if (hata) {
    console.error('Veri getirilirken hata oluştu:', hata);
  } else {
    console.log('Veri başarıyla getirildi:', veri);
  }
});

Callback’ler basit asenkron senaryolar için yeterli olsa da, karmaşık veya birbirine bağımlı asenkron işlemler zinciri oluşturulduğunda ciddi sorunlara yol açabilirler:

* Callback Hell (Piramit Felaketi): Birden fazla iç içe callback, kodun okunmasını ve bakımını son derece zorlaştırır. Her yeni asenkron adım, bir önceki callback’in içine girer ve kod sağa doğru uzayan bir “piramit” şeklini alır.
* Hata Yönetimi Zorluğu: İç içe geçmiş callback’lerde hataları doğru bir şekilde yakalamak ve yaymak karmaşık hale gelir. Her seviyede hata kontrolü yapmak gerekir.
* Akış Kontrolü Karmaşıklığı: Birden fazla asenkron işlemin paralel olarak çalışması, belirli bir sırayla tamamlanması veya yalnızca birinin sonucunun beklenmesi gibi senaryoları yönetmek callback’lerle oldukça zordur.

Bu sınırlılıklar, asenkron programlamayı daha düzenli ve anlaşılır hale getirecek yeni bir soyutlamaya olan ihtiyacı doğurdu: Promise’lar.

Promise Nedir?

Bir Promise (Türkçe’de “söz” veya “vaat” anlamına gelir), asenkron bir işlemin nihai sonucunu (başarılı tamamlanma değeri veya başarısızlık nedeni) temsil eden bir JavaScript nesnesidir. Basitçe söylemek gerekirse, bir Promise, “Şu anda tamamlanmamış bir işlem için size gelecekte bir değer sözü veriyorum. Bu değer ya başarıyla dönecek ya da bir hatayla sonuçlanacak.” der.

Promise’lar, asenkron işlemleri callback’ler yerine daha düzenli ve zincirlenebilir bir şekilde yönetmek için bir yapı sağlar.

Promise Durumları (States)

Bir Promise, yaşam döngüsü boyunca üç farklı durumda bulunabilir:

1. pending (Beklemede): Promise’ın başlangıç durumudur. Asenkron işlem henüz tamamlanmamış veya reddedilmemiştir.
2. fulfilled (Yerine Getirildi / Başarılı): Asenkron işlem başarıyla tamamlandığında Promise bu duruma geçer. Bu durumda, Promise’ın bir “değeri” (value) vardır.
3. rejected (Reddedildi / Başarısız): Asenkron işlem bir hata nedeniyle başarısız olduğunda Promise bu duruma geçer. Bu durumda, Promise’ın bir “nedeni” (reason) veya hata objesi vardır.

Önemli bir özellik, bir Promise’ın pending durumundan fulfilled veya rejected durumuna yalnızca bir kez geçebilmesidir. Bir kez yerine getirildiğinde veya reddedildiğinde, Promise’ın durumu ve değeri (veya nedeni) asla değişmez. Bu, Promise’ların “değişmez” (immutable) olduğu anlamına gelir.

Bir Promise Oluşturma

Yeni bir Promise oluşturmak için new Promise() yapıcısını kullanırız. Bu yapıcı, iki argüman alan bir yürütücü fonksiyonu (executor function) alır: resolve ve reject.

const benimIlkPromise = new Promise((resolve, reject) => {
  // Asenkron bir işlem simüle edelim (örneğin, bir sunucu isteği)
  const basariDurumu = Math.random() > 0.5; // %50 ihtimalle başarılı

  setTimeout(() => {
    if (basariDurumu) {
      resolve('İşlem başarıyla tamamlandı!'); // Promise'ı başarılı olarak işaretle
    } else {
      reject('İşlem sırasında bir hata oluştu.'); // Promise'ı başarısız olarak işaretle
    }
  }, 2000); // 2 saniye sonra sonuçlanacak
});

console.log('Promise oluşturuldu, beklemede...');

// Promise'ın sonucunu dinlemek için .then() ve .catch() kullanırız
benimIlkPromise
  .then((mesaj) => {
    console.log('Başarılı:', mesaj); // Promise başarılı olduğunda çalışır
  })
  .catch((hata) => {
    console.error('Hata:', hata); // Promise başarısız olduğunda çalışır
  });

console.log('Promise dinleyicileri eklendi.');

Yukarıdaki örnekte:
* new Promise((resolve, reject) => { ... }): Yeni bir Promise oluştururuz. İçindeki fonksiyon hemen çalışır.
* resolve(değer): Asenkron işlem başarıyla tamamlandığında çağrılır. Promise’ın durumunu fulfilled olarak değiştirir ve belirtilen değer ile sonuçlandırır.
* reject(neden): Asenkron işlem bir hata nedeniyle başarısız olduğunda çağrılır. Promise’ın durumunu rejected olarak değiştirir ve belirtilen neden (genellikle bir Error objesi) ile sonuçlandırır.

Promise Zincirleme (Chaining)

Promise’ların en güçlü özelliklerinden biri, .then(), .catch() ve .finally() metodları aracılığıyla zincirlenebilmeleridir. Her .then() çağrısı, yeni bir Promise döndürür ve bu da ardışık asenkron işlemleri düz bir akışta yazmamızı sağlar. Bu, “Callback Hell” sorununu ortadan kaldırır.

.then() Metodu

.then() metodu, bir Promise başarıyla yerine getirildiğinde (fulfilled) çağrılacak bir callback fonksiyonu kaydetmek için kullanılır. İki opsiyonel argüman alabilir:

1. onFulfilled: Promise başarıyla tamamlandığında çağrılacak fonksiyon. Promise’ın başarı değerini argüman olarak alır.
2. onRejected: Promise reddedildiğinde çağrılacak fonksiyon. Promise’ın hata nedenini argüman olarak alır. (Genellikle hata yakalamak için .catch() tercih edilir.)

function adim1() {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log('Adım 1 tamamlandı.');
      resolve(10); // Sonucu bir sonraki adıma aktar
    }, 1000);
  });
}

function adim2(oncekiSonuc) {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log(Adım 2 tamamlandı, önceki sonuç: ${oncekiSonuc});
      resolve(oncekiSonuc * 2);
    }, 1000);
  });
}

function adim3(oncekiSonuc) {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log(Adım 3 tamamlandı, önceki sonuç: ${oncekiSonuc});
      resolve(oncekiSonuc + 5);
    }, 1000);
  });
}

adim1()
  .then(sonuc1 => adim2(sonuc1)) // adim1'in sonucunu adim2'ye aktar
  .then(sonuc2 => adim3(sonuc2)) // adim2'nin sonucunu adim3'e aktar
  .then(nihaiSonuc => {
    console.log('Tüm adımlar tamamlandı, nihai sonuç:', nihaiSonuc);
  })
  .catch(hata => {
    console.error('Zincirde bir hata oluştu:', hata);
  });

Bu örnekte, adim1, adim2 ve adim3 fonksiyonları sırayla çalışır. Her .then() çağrısı, kendisinden önceki Promise’ın sonucunu alır ve yeni bir Promise döndürerek zincirlemenin devam etmesini sağlar. Eğer bir .then() callback’i bir değer döndürürse, bu değer bir sonraki .then() callback’ine geçirilir. Eğer bir Promise döndürürse, bir sonraki .then() bu Promise’ın çözülmesini bekler.

.catch() Metodu

.catch() metodu, bir Promise reddedildiğinde (rejected) çağrılacak bir callback fonksiyonu kaydetmek için kullanılır. Temel olarak promise.then(null, onRejected) ile aynıdır, ancak daha okunabilir bir hata yakalama mekanizması sağlar.

function basarisizIslem() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(new Error('Bu işlem kasıtlı olarak başarısız oldu!'));
    }, 1000);
  });
}

basarisizIslem()
  .then(sonuc => {
    console.log('Başarılı:', sonuc); // Bu asla çalışmaz
  })
  .catch(hata => {
    console.error('Hata yakalandı:', hata.message); // Hata burada yakalanır
  });

// Zincirleme sonunda .catch() kullanmak en yaygın ve önerilen yöntemdir.
// Bu, zincirdeki herhangi bir Promise'dan gelen hatayı yakalar.
new Promise((resolve, reject) => {
    setTimeout(() => reject('İlk hata'), 500);
  })
  .then(() => new Promise((resolve, reject) => setTimeout(() => resolve('İkinci adım'), 500)))
  .then(() => console.log('Başarılı'))
  .catch(hata => console.error('Tüm zincir için hata:', hata));

Bir Promise zincirinde, bir Promise reddedildiğinde, kontrol zincirdeki bir sonraki .catch() handler’ına atlar. Eğer zincirde hiç .catch() yoksa, hata “yakalanmayan Promise reddi” (unhandled promise rejection) olarak yayılır ve genellikle konsolda bir uyarı veya hata ile sonuçlanır.

.finally() Metodu (ES2018)

.finally() metodu, Promise’ın nihai durumundan (fulfilled veya rejected) bağımsız olarak, Promise çözüldüğünde her zaman çağrılacak bir callback fonksiyonu kaydetmek için kullanılır. Genellikle temizlik işlemleri (örneğin, yükleme göstergesini gizleme, kaynakları serbest bırakma) için idealdir.

function birIslem() {
  return new Promise((resolve, reject) => {
    const basarili = Math.random() > 0.5;
    setTimeout(() => {
      if (basarili) {
        resolve('İşlem başarıyla bitti.');
      } else {
        reject(new Error('İşlem başarısız oldu.'));
      }
    }, 1500);
  });
}

birIslem()
  .then(sonuc => {
    console.log(sonuc);
  })
  .catch(hata => {
    console.error('Hata:', hata.message);
  })
  .finally(() => {
    console.log('İşlem tamamlandı (başarılı veya başarısız).');
    // Burada loading spinner'ı gizleyebilir veya başka temizlik yapabiliriz.
  });

.finally() callback’i, Promise’ın değerini veya hata nedenini argüman olarak almaz. Kendisi de bir Promise döndürebilir ve bu Promise’ın çözülmesini bekler.

Promise API’leri

JavaScript, birden fazla Promise’ı aynı anda yönetmek için bazı kullanışlı statik Promise metodları sunar.

Promise.all(iterable)

Promise.all(), bir dizi Promise’ı (veya Promise benzeri değerleri) argüman olarak alır ve bu Promise’ların hepsi başarılı bir şekilde tamamlandığında çözülen yeni bir Promise döndürür. Sonuç, orijinal Promise’ların sırasına göre değerlerini içeren bir dizidir.

Eğer verilen Promise’lardan herhangi biri reddedilirse, Promise.all() hemen reddedilir ve ilk reddedilen Promise’ın hata nedenini döndürür.

const promise1 = Promise.resolve(3);
const promise2 = 42; // Otomatik olarak çözümlenmiş bir Promise gibi davranır
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3])
  .then(values => {
    console.log('Promise.all sonuçları:', values); // [3, 42, "foo"]
  })
  .catch(error => {
    console.error('Promise.all hatası:', error);
  });

// Hata senaryosu
const p1 = new Promise(resolve => setTimeout(() => resolve('P1'), 1000));
const p2 = new Promise((resolve, reject) => setTimeout(() => reject('P2 Hatası'), 500)); // Daha hızlı reddedilir
const p3 = new Promise(resolve => setTimeout(() => resolve('P3'), 1500));

Promise.all([p1, p2, p3])
  .then(values => console.log(values))
  .catch(error => console.error('Promise.all hata (P2):', error)); // P2 Hatası

Promise.all() genellikle birden fazla API çağrısını paralel olarak yapmanız ve hepsinin sonucuna ihtiyacınız olduğunda kullanılır.

Promise.race(iterable)

Promise.race(), bir dizi Promise’ı argüman olarak alır ve bu Promise’lardan herhangi biri ilk çözüldüğünde veya reddedildiğinde çözülen/reddedilen yeni bir Promise döndürür. “Yarışı” kazanan Promise’ın sonucunu (değer veya hata) döndürür.

const pA = new Promise(resolve => setTimeout(() => resolve('A bitti'), 1000));
const pB = new Promise(resolve => setTimeout(() => resolve('B bitti'), 50));
const pC = new Promise((resolve, reject) => setTimeout(() => reject('C hata'), 200));

Promise.race([pA, pB, pC])
  .then(value => {
    console.log('Promise.race kazananı:', value); // B bitti
  })
  .catch(error => {
    console.error('Promise.race hatası:', error);
  });

// Hata kazanan senaryosu
const pX = new Promise(resolve => setTimeout(() => resolve('X bitti'), 1000));
const pY = new Promise((resolve, reject) => setTimeout(() => reject('Y hata'), 50));

Promise.race([pX, pY])
  .then(value => console.log(value))
  .catch(error => console.error('Promise.race hata (Y):', error)); // Y hata

Promise.race() genellikle bir işlemin belirli bir süre içinde tamamlanmasını beklemek (zaman aşımı) veya aynı görevi farklı kaynaklardan isteyip en hızlı yanıtı almak istediğinizde kullanılır.

Promise.allSettled(iterable) (ES2020)

Promise.allSettled(), bir dizi Promise’ı argüman olarak alır ve tüm Promise’lar tamamlandığında (başarılı veya başarısız) çözülen yeni bir Promise döndürür. Sonuç, her bir Promise’ın durumunu ve değerini/nedenini açıklayan bir nesne dizisidir.

Her sonuç nesnesi şu formattadır:
* { status: 'fulfilled', value: sonuçDeğeri }
* { status: 'rejected', reason: hataNedeni }

const p1 = new Promise(resolve => setTimeout(() => resolve('P1 başarılı'), 1000));
const p2 = new Promise((resolve, reject) => setTimeout(() => reject('P2 başarısız'), 500));
const p3 = Promise.resolve('P3 hemen başarılı');

Promise.allSettled([p1, p2, p3])
  .then(results => {
    console.log('Promise.allSettled sonuçları:');
    results.forEach((result, index) => {
      if (result.status === 'fulfilled') {
        console.log(Promise ${index + 1} başarılı:, result.value);
      } else {
        console.error(Promise ${index + 1} başarısız:, result.reason);
      }
    });
  });
/*
Çıktı:
Promise.allSettled sonuçları:
Promise 3 başarılı: P3 hemen başarılı
Promise 2 başarısız: P2 başarısız
Promise 1 başarılı: P1 başarılı
*/

Promise.allSettled() genellikle tüm işlemlerin sonucunu bilmeniz gerektiğinde, bazıları başarısız olsa bile, kullanılır.

Promise.any(iterable) (ES2021)

Promise.any(), bir dizi Promise’ı argüman olarak alır ve bu Promise’lardan herhangi biri başarılı bir şekilde çözüldüğünde çözülen yeni bir Promise döndürür. İlk başarılı olan Promise’ın değerini döndürür.

Eğer verilen tüm Promise’lar reddedilirse, Promise.any() reddedilir ve bir AggregateError nesnesi döndürür. Bu hata nesnesi, tüm reddedilme nedenlerini içeren bir errors özelliğine sahiptir.

const pR1 = new Promise((resolve, reject) => setTimeout(() => reject('Hata 1'), 100));
const pR2 = new Promise((resolve, reject) => setTimeout(() => reject('Hata 2'), 200));
const pS3 = new Promise(resolve => setTimeout(() => resolve('Başarılı 3'), 300));

Promise.any([pR1, pR2, pS3])
  .then(value => {
    console.log('Promise.any kazananı:', value); // Başarılı 3
  })
  .catch(error => {
    console.error('Promise.any hatası:', error);
  });

// Tüm Promise'ların reddedildiği senaryo
const pFail1 = new Promise((resolve, reject) => setTimeout(() => reject('A başarısız'), 100));
const pFail2 = new Promise((resolve, reject) => setTimeout(() => reject('B başarısız'), 200));

Promise.any([pFail1, pFail2])
  .then(value => console.log(value))
  .catch(error => {
    console.error('Promise.any tümü başarısız:', error.errors); // ['A başarısız', 'B başarısız']
  });

Promise.any() genellikle birden fazla kaynaktan veri almayı denediğinizde ve bunlardan en az birinin başarılı olmasını beklediğinizde kullanışlıdır.

Promise.resolve(value) ve Promise.reject(reason)

Bu metodlar, hemen çözümlenmiş veya hemen reddedilmiş Promise’lar oluşturmak için kullanılır.

* Promise.resolve(value): Verilen value ile hemen başarılı bir şekilde çözümlenmiş bir Promise döndürür.
* Promise.reject(reason): Verilen reason ile hemen reddedilmiş bir Promise döndürür.

Promise.resolve('Hemen başarılı!')
  .then(val => console.log(val)); // Hemen başarılı!

Promise.reject(new Error('Hemen başarısız!'))
  .catch(err => console.error(err.message)); // Hemen başarısız!

Bu metodlar, bazen bir fonksiyonun her zaman bir Promise döndürmesini garanti etmek istediğinizde veya Promise tabanlı bir API’yi test ederken kullanışlıdır.

Hata Yönetimi

Promise’larda hata yönetimi, .catch() metodunun kullanımıyla oldukça basitleşir. Bir Promise zincirinde herhangi bir yerde bir hata meydana gelirse (yani bir Promise reddedilirse veya bir callback içinde bir hata fırlatılırsa), kontrol zincirdeki bir sonraki .catch() handler’ına atlar.

function rastgeleHataUret() {
  return new Promise((resolve, reject) => {
    const shouldFail = Math.random() > 0.5;
    setTimeout(() => {
      if (shouldFail) {
        reject(new Error('Rastgele bir hata oluştu!'));
      } else {
        resolve('İşlem başarıyla devam ediyor.');
      }
    }, 500);
  });
}

rastgeleHataUret()
  .then(sonuc => {
    console.log('İlk adım:', sonuc);
    return rastgeleHataUret(); // İkinci bir Promise döndür
  })
  .then(sonuc => {
    console.log('İkinci adım:', sonuc);
    // Burada senkron bir hata fırlatabiliriz
    if (Math.random() > 0.5) {
      throw new Error('İkinci adımda senkron hata!');
    }
    return 'Tüm adımlar tamamlandı.';
  })
  .then(finalSonuc => {
    console.log('Nihai sonuç:', finalSonuc);
  })
  .catch(hata => {
    console.error('Zincirde genel bir hata yakalandı:', hata.message);
    // Hata yakalandıktan sonra zinciri devam ettirmek istiyorsak
    // burada bir değer döndürebiliriz (başarılı Promise gibi davranır)
    // return 'Hatadan sonra devam edildi.';
  })
  .then(sonrakiAdim => {
    // Eğer .catch() bir değer döndürürse bu adım çalışır
    // console.log('Catch sonrası:', sonrakiAdim);
  })
  .finally(() => {
    console.log('Tüm akış sona erdi.');
  });

Yukarıdaki örnekte, zincirdeki herhangi bir adımda bir hata oluşsa veya fırlatılsa bile, en sondaki .catch() bloğu bu hatayı yakalayacaktır. Bu, her bir asenkron işlem için ayrı ayrı hata kontrolü yapma ihtiyacını ortadan kaldırır ve kodun çok daha temiz olmasını sağlar.

Yakalanmayan Promise Redleri (Unhandled Promise Rejections): Eğer bir Promise reddedilir ve zincirde bu hatayı yakalayacak hiçbir .catch() handler’ı yoksa, bu bir “yakalanmayan Promise reddi” olarak kabul edilir. Tarayıcılarda bu genellikle konsolda bir uyarı veya hata mesajıyla sonuçlanır (unhandledrejection olayı). Node.js’te ise uygulamanın çökmesine neden olabilir (unhandledRejection olayı). Bu nedenle, her zaman Promise zincirlerinizin sonunda bir .catch() bloğu bulundurmanız şiddetle tavsiye edilir.

Async/Await ile Promise’ları Daha Kolay Kullanma

ES2017 ile tanıtılan async ve await anahtar kelimeleri, Promise’lar üzerinde daha da okunabilir ve senkron görünümlü kod yazmamızı sağlayan sentaktik şekerdir (syntactic sugar). Temelinde Promise’lara dayanır, ancak onları kullanma şeklimizi kökten değiştirir.

async Anahtar Kelimesi

Bir fonksiyonun önüne async anahtar kelimesini eklemek, o fonksiyonun her zaman bir Promise döndürmesini sağlar. Fonksiyon içinde return ile dönen herhangi bir değer, başarılı bir şekilde çözümlenmiş bir Promise’ın değeri olur. Fonksiyon içinde fırlatılan bir hata ise reddedilmiş bir Promise’a dönüşür.

async function selamVer() {
  return 'Merhaba Dünya!'; // Promise.resolve('Merhaba Dünya!') döndürür
}

selamVer().then(mesaj => console.log(mesaj)); // Merhaba Dünya!

async function hataFirlat() {
  throw new Error('Async fonksiyonda hata!'); // Promise.reject(Error) döndürür
}

hataFirlat().catch(hata => console.error(hata.message)); // Async fonksiyonda hata!

await Anahtar Kelimesi

await anahtar kelimesi, yalnızca async fonksiyonlar içinde kullanılabilir. Bir Promise’ın çözülmesini (yani fulfilled veya rejected durumuna geçmesini) bekler. Promise çözüldüğünde, await Promise’ın değerini döndürür. Eğer Promise reddedilirse, await bir hata fırlatır.

Bu, asenkron kodu senkron kod gibi ardışık bir şekilde yazmamızı sağlar.

function gecikmeliMesaj(mesaj, sure) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(mesaj);
    }, sure);
  });
}

async function siraliIslemler() {
  console.log('İşlemler başlıyor...');
  try {
    const mesaj1 = await gecikmeliMesaj('İlk mesaj', 1000);
    console.log(mesaj1); // 1 saniye sonra: İlk mesaj

    const mesaj2 = await gecikmeliMesaj('İkinci mesaj', 500);
    console.log(mesaj2); // 0.5 saniye sonra: İkinci mesaj

    const mesaj3 = await gecikmeliMesaj('Üçüncü mesaj', 700);
    console.log(mesaj3); // 0.7 saniye sonra: Üçüncü mesaj

    console.log('Tüm işlemler tamamlandı.');
    return 'Başarılı!';
  } catch (hata) {
    console.error('Bir hata oluştu:', hata);
    throw hata; // Hatayı tekrar fırlatarak async fonksiyonun reddedilmesini sağlarız
  }
}

siraliIslemler()
  .then(sonuc => console.log('Async fonksiyon sonucu:', sonuc))
  .catch(hata => console.error('Async fonksiyon hatası:', hata));

Yukarıdaki örnekte, await anahtar kelimesi sayesinde gecikmeliMesaj Promise’ları senkron bir şekilde çağrılmış gibi görünür. Her await, bir önceki Promise çözülene kadar fonksiyonun yürütülmesini duraklatır (ancak ana iş parçacığını bloke etmez).

Async/Await ile Hata Yönetimi

async/await kullanırken hata yönetimi, geleneksel try...catch blokları ile yapılır. Bir await ifadesi bir reddedilmiş Promise ile karşılaşırsa, bir hata fırlatır ve bu hata try...catch bloğu tarafından yakalanabilir.

function hataYapanIslem(basariliMi) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (basariliMi) {
        resolve('Veri alındı.');
      } else {
        reject(new Error('Veri alma hatası!'));
      }
    }, 1000);
  });
}

async function veriIsle() {
  try {
    const veri = await hataYapanIslem(false); // Burası hata fırlatacak
    console.log(veri);
  } catch (hata) {
    console.error('Hata yakalandı:', hata.message); // Hata yakalandı: Veri alma hatası!
  } finally {
    console.log('İşlem sona erdi.');
  }
}

veriIsle();

Async/Await ile Paralel İşlemler

await her ne kadar kodu senkron gibi gösterse de, paralel işlemlere engel değildir. Birden fazla Promise’ı paralel çalıştırmak ve sonuçlarını beklemek için Promise.all()‘ı await ile birlikte kullanabiliriz:

async function paralelVeriGetir() {
  console.log('Paralel veri getirme başlıyor...');
  try {
    const [kullaniciVerisi, urunVerisi] = await Promise.all([
      fetch('https://api.example.com/users/1').then(res => res.json()),
      fetch('https://api.example.com/products/abc').then(res => res.json())
    ]);

    console.log('Kullanıcı Verisi:', kullaniciVerisi);
    console.log('Ürün Verisi:', urunVerisi);
    console.log('Tüm veriler paralel olarak getirildi.');
  } catch (hata) {
    console.error('Paralel veri getirme hatası:', hata);
  }
}

// paralelVeriGetir(); // Gerçek bir API olmadığı için çalışmayacaktır.

async/await, Promise tabanlı kodu yazmanın en modern ve okunabilir yoludur. Çoğu yeni JavaScript projesinde asenkron kod yazarken tercih edilen yaklaşımdır.

Promise’lar ve Gerçek Dünya Uygulamaları

Promise’lar, modern web geliştirmenin birçok alanında temel bir rol oynar:

* API Çağrıları: fetch API’si varsayılan olarak Promise tabanlıdır. Axios gibi HTTP istemci kütüphaneleri de Promise’ları yoğun bir şekilde kullanır.
* Veritabanı İşlemleri: Node.js’teki çoğu veritabanı sürücüsü (MongoDB, PostgreSQL, MySQL vb.) asenkron işlemleri Promise’lar aracılığıyla yönetir.
* Dosya Okuma/Yazma: Node.js’in fs/promises modülü, dosya sistemi işlemlerini Promise tabanlı API’lerle sunar.
* Zamanlayıcılar: setTimeout veya setInterval gibi yerleşik fonksiyonları Promise’lara dönüştüren yardımcı fonksiyonlar yazılabilir.
* Animasyonlar ve Gecikmeli İşlemler: Kullanıcı arayüzünde belirli bir gecikmeyle veya bir dizi animasyonun sırayla çalışması gerektiğinde Promise’lar kullanılır.

Anti-Pattern’ler ve En İyi Uygulamalar

Promise’ları etkili bir şekilde kullanmak için bazı anti-pattern’lerden kaçınmak ve en iyi uygulamaları takip etmek önemlidir.

Anti-Pattern’ler:

* Callback Hell’e geri dönmek: Promise’ları new Promise içine gereksiz yere sarmak veya .then() içinde tekrar callback tarzı kod yazmak.
* .catch() kullanmamak: Zincirdeki hataları yakalamamak, uygulamanızın beklenmedik şekillerde çökmesine veya hataların sessizce yutulmasına neden olabilir.
* Promise’ları gereksiz yere sarmak: Zaten Promise döndüren bir fonksiyonu new Promise ile sarmalamak gereksizdir ve kod karmaşıklığını artırır.
* Promise içinden senkron hata fırlatmak: new Promise((resolve, reject) => { throw new Error('Hata'); }) yerine reject(new Error('Hata')) kullanmak daha tutarlıdır.

En İyi Uygulamalar:

* Her zaman .catch() kullanın: Özellikle uzun Promise zincirlerinin sonunda veya async fonksiyonlarınızın ana gövdesinde bir try...catch bloğu bulundurun.
* Promise’ları zincirleyin: Okunabilirliği ve yönetilebilirliği artırmak için .then() ve .catch() metodlarını kullanarak Promise’ları zincirleyin.
* Async/await’i tercih edin: Mümkün olduğunda, Promise tabanlı asenkron kodu daha okunabilir ve senkron görünümlü hale getirmek için async/await kullanın.
* Anlamlı hata mesajları ve türleri kullanın: Hataları yakaladığınızda, sorunun kaynağını kolayca anlamak için Error nesnelerini ve açıklayıcı hata mesajlarını kullanın.
* Modüler ve yeniden kullanılabilir Promise’lar oluşturun: Tek bir sorumluluğu olan Promise döndüren fonksiyonlar yazarak kodunuzu daha yönetilebilir hale getirin.
* Promise’ları erken döndürün: Bir Promise döndüren bir fonksiyon yazıyorsanız, Promise’ı mümkün olduğunca erken return edin, böylece zincirleme yeteneği korunur.

Sonuç

JavaScript Promise’ları, asenkron programlamayı yönetmek için devrim niteliğinde bir soyutlama sunmuştur. Callback’lerin getirdiği “Callback Hell” sorununa zarif bir çözüm getirerek, asenkron kodun okunabilirliğini, hata yönetimini ve akış kontrolünü önemli ölçüde iyileştirmiştir.

Promise’ların durumlarını anlamak (pending, fulfilled, rejected), .then(), .catch() ve .finally() metodlarıyla nasıl zincirleneceklerini bilmek, asenkron JavaScript’i etkili bir şekilde kullanmanın temelini oluşturur. Promise.all(), Promise.race(), Promise.allSettled() ve Promise.any() gibi statik metodlar ise daha karmaşık asenkron senaryoları yönetmek için güçlü araçlar sunar.

Son olarak, async ve await anahtar kelimeleri, Promise’lar üzerinde bir katman daha ekleyerek, asenkron kodu senkron kod gibi yazma imkanı sağlamış ve modern JavaScript’te asenkron programlamayı neredeyse zahmetsiz hale getirmiştir.

Günümüz web geliştirme dünyasında, API çağrılarından veritabanı işlemlerine kadar birçok alanda asenkron işlemler kaçınılmazdır. JavaScript Promise’larını ve async/await yapısını derinlemesine anlamak, daha sağlam, bakımı kolay ve performanslı uygulamalar geliştirmek için her JavaScript geliştiricisinin sahip olması gereken kritik bir beceridir. Bu araçlar, JavaScript’in tek iş parçacıklı doğasını avantaja çevirerek, kullanıcı dostu ve hızlı uygulamaların kapısını aralar.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Gönder

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.
Mobil sürümden çık