Takip et

Async JS: Swiggy, Zomato ve Yemek Siparişleri ile Adım Adım

Modern web ve mobil uygulamaların kalbinde yatan asenkron JavaScript kavramı, Swiggy veya Zomato gibi popüler yemek siparişi platformlarının sorunsuz çalışmasını sağlayan temel mekanizmalardan biridir. Bir yemeği sipariş ederken uygulamanın donup kalmamasını, arka planda farklı işlemler yürütülürken bile kullanıcı arayüzünün akıcı kalmasını sağlayan bu büyülü dünyayı keşfetmeye hazır mısınız?

Hayatımızdaki birçok sistem gibi, yazılım dünyasında da işlemlerin nasıl yürütüldüğünü açıklayan iki temel düzen vardır: Senkron (eş zamanlı) ve Asenkron (eş zamansız). Bu kavramları anlamak için gelin, kendimizi yoğun bir restorandaki yemek siparişi sürecine bırakalım.

Senkron Restoran Deneyimi: Her Şey Sırayla

Senkron bir restoranda, bir müşteri sipariş verdiğinde mutfaktaki aşçı o yemeği tamamen bitirmeden başka hiçbir işe başlamaz. Diyelim ki peynirli tost ve ardından çorba sipariş ettiniz. Aşçı önce tostu hazırlayacak, pişirecek ve servis edilebilir hale getirecek. Bu süreçte mutfakta başka hiçbir aktivite gerçekleşemez. Tost masanıza gelmeden çorbanın yapımına başlanmaz. Bu, bir yandan bakıldığında basit ve düzenli görünebilir; ancak restoranın kapasitesini ciddi şekilde sınırlar ve bekleme sürelerini uzatır. Müşteri olarak siz de ilk siparişiniz tamamlanana kadar diğer işlerinizi yapamaz, sadece beklemek zorunda kalırsınız.

JavaScript dünyasında da durum benzerdir. JavaScript, varsayılan olarak “tek iş parçacıklı” (single-threaded) bir dildir. Bu, bir anda sadece tek bir görevi yürütebileceği anlamına gelir. Eğer senkron bir işlem uzun sürerse (örneğin, büyük bir dosyayı indirmek veya karmaşık bir hesaplama yapmak), bu işlem bitene kadar diğer tüm görevler (kullanıcı arayüzünü güncellemek, tıklamalara yanıt vermek vb.) bloke olur. Yani, uygulamanız donar ve kullanıcının etkileşimi yanıt vermez. İşte bu “donma” durumu, Swiggy gibi uygulamalarda asla istemeyeceğimiz bir durumdur.

Asenkron Restoran Deneyimi: Akıllı İş Bölümü ve Paralel Yönetim

Şimdi de asenkron bir restorana geçelim. Swiggy veya Zomato’nun mutfaklarını düşünün. Siz peynirli tost ve çorba sipariş ettiğinizde, mutfaktaki aşçı tostu fırına verir ve pişmesini beklerken hemen çorbayı hazırlamaya başlar. Hatta belki de başka bir masanın salatasını yapmaya bile koyulur. Aşçı, “toast piştiğinde beni uyar” veya “çorba kaynadığında haber ver” gibi talimatlar verir ve bu görevler arka planda yürütülürken diğer işlere odaklanır. Bir yemek hazır olduğunda, garson onu hemen masanıza getirir. Bu sırada siz diğer yemeğinizi beklerken telefonunuza bakabilir, arkadaşlarınızla sohbet edebilir veya uygulamanın başka yerlerini gezebilirsiniz. Restoran hiçbir zaman “durup” tek bir işi bitirmeyi beklemez.

Asenkron JavaScript de tam olarak böyle çalışır. Uzun süren işlemleri (örneğin, Swiggy’den restoran listesini çekmek, bir kullanıcının konumunu belirlemek veya ödeme işlemini yapmak gibi ağ istekleri) başlatır ve bunları “arka plana” iter. Bu sırada ana iş parçacığı serbest kalır ve kullanıcı arayüzü etkileşimlerine yanıt vermeye devam edebilir. Uzun süren işlem tamamlandığında, JavaScript’in “Olay Döngüsü” (Event Loop) bu işlemi tespit eder ve ana iş parçacığı boşta olduğunda sonucunu işler. Böylece uygulamanız hiçbir zaman donmaz, kullanıcı her zaman akıcı bir deneyim yaşar.

Bu temel fark, modern web uygulamalarının neden bu kadar hızlı ve tepkisel olduğunu anlamak için kritik öneme sahiptir. Senkron blokajlardan kaçınmak, kullanıcı deneyiminin anahtarıdır. Dolayısıyla, Swiggy’de bir yemek siparişi verdiğinizde arayüzün akıcı kalmasının arkasındaki sır, JavaScript’in asenkron yetenekleridir.

Uzman İpucu: JavaScript’in tek iş parçacıklı doğasını anlamak, asenkron programlamanın neden bu kadar gerekli olduğunu kavramanın ilk adımıdır. Unutmayın, JS aynı anda sadece bir işi “aktif olarak” yapar, ancak asenkron mekanizmalar sayesinde birçok işi “aynı anda bekleyebilir ve yönetebilir”.

Callback Fonksiyonları: Garsonlar Nasıl Haber Veriyor?

Asenkron programlamanın ilk ve en temel mekanizmalarından biri callback (geri arama) fonksiyonlarıdır. Adından da anlaşılacağı gibi, bir callback fonksiyonu, belirli bir olayın gerçekleşmesi veya bir işlemin tamamlanması durumunda çağrılması gereken bir fonksiyondur. Yemek siparişi analogisine geri dönelim.

Garsonun Not Defteri ve Geri Arama Sistemi

Bir restoranda garsona sipariş verirken, yemeğiniz hazır olduğunda size haber verilmesini istersiniz. Garson, “Yemek hazır olduğunda masanıza getireceğim” veya “Bu numarayı arayarak size haber vereceğim” der. İşte bu “haberdar etme” eylemi, callback fonksiyonunun çalışma prensibine benzer. Siz yemeğinizi beklerken kendi işlerinize devam edersiniz (telefonunuza bakmak, arkadaşlarınızla sohbet etmek gibi). Yemek hazırlandığında, mutfak garsona haber verir ve garson da size yemeği getirir. Bu durumda, garsonun size yemeği getirme eylemi, bir callback fonksiyonunun çağrılması gibidir.

JavaScript’te callback fonksiyonları, özellikle eski kod tabanlarında veya basit asenkron işlemler için sıkça kullanılır. Örneğin, bir API isteği gönderdiğinizde, isteğin cevabı geldiğinde çalışacak bir fonksiyonu callback olarak tanımlarsınız. En bilinen örneklerden biri setTimeout fonksiyonudur:


// Yemek hazırlama simülasyonu
function yemekHazirla(yemekAdi, callback) {
  console.log(${yemekAdi} hazırlanıyor...);
  setTimeout(() => {
    console.log(${yemekAdi} hazır!);
    callback(yemekAdi); // Yemek hazır olduğunda callback'i çağır
  }, Math.random() * 3000 + 1000); // 1 ila 4 saniye arası rastgele süre
}

// Siparişleri verelim
yemekHazirla("Çorba", (hazirYemek) => {
  console.log(Müşteriye "${hazirYemek}" servis edildi.);
});

yemekHazirla("Salata", (hazirYemek) => {
  console.log(Müşteriye "${hazirYemek}" servis edildi.);
});

console.log("Diğer müşterilere bakmaya devam ediyorum...");

Yukarıdaki örnekte, yemekHazirla fonksiyonu asenkron bir işlemi (yemeği hazırlama) simüle eder. Bu işlem tamamlandığında, fonksiyona parametre olarak verilen callback fonksiyonu çağrılır. Gördüğünüz gibi, yemekHazirla çağrıldıktan hemen sonra "Diğer müşterilere bakmaya devam ediyorum..." mesajı yazdırılır. Bu, ana iş parçacığının bloke olmadığını, diğer işlere devam ettiğini gösterir.

Callback Cehennemi (Callback Hell): Labirentteki Kayıp Siparişler

Callback'ler basit senaryolar için harika olsa da, karmaşık veya birbirine bağımlı asenkron işlemleri yönetmeye çalıştığımızda "callback cehennemi" (callback hell) denen bir durumla karşılaşırız. Bu, iç içe geçmiş callback fonksiyonlarının oluşturduğu piramit benzeri, okunması ve bakımı zor bir kod yapısıdır.

Şöyle düşünün: Swiggy'den bir sipariş verirken önce restoranı seçmeniz, sonra menüyü yüklemeniz, ardından yemeği sepete eklemeniz, sonra ödeme yöntemini seçmeniz ve son olarak siparişi onaylamanız gerekir. Her adım bir öncekinin sonucuna bağlıdır ve her bir adım asenkron bir işlem olabilir.


function restoranSec(callback) {
  console.log("Restoranlar yükleniyor...");
  setTimeout(() => {
    const restoran = "Burgercü Baba";
    console.log(${restoran} seçildi.);
    callback(null, restoran); // Hata yok, restoran bilgisi
  }, 1000);
}

function menuyuGetir(restoran, callback) {
  console.log(${restoran} menüsü yükleniyor...);
  setTimeout(() => {
    const menu = ["Hamburger", "Patates", "Kola"];
    console.log("Menü yüklendi.");
    callback(null, menu);
  }, 1500);
}

function sepeteEkle(menu, urun, callback) {
  console.log(${urun} sepete ekleniyor...);
  setTimeout(() => {
    if (menu.includes(urun)) {
      console.log(${urun} sepete eklendi.);
      callback(null, { urun, miktar: 1 });
    } else {
      callback("Ürün menüde yok!", null);
    }
  }, 800);
}

function siparisiOnayla(sepet, callback) {
  console.log("Sipariş onaylanıyor...");
  setTimeout(() => {
    const siparisID = Math.floor(Math.random() * 10000);
    console.log(Siparişiniz ${siparisID} ile onaylandı!);
    callback(null, siparisID);
  }, 2000);
}

// Callback Cehennemi örneği:
restoranSec((err, restoran) => {
  if (err) return console.error(err);
  menuyuGetir(restoran, (err, menu) => {
    if (err) return console.error(err);
    sepeteEkle(menu, "Hamburger", (err, sepetUrun) => {
      if (err) return console.error(err);
      sepeteEkle(menu, "Patates", (err, sepetUrun2) => { // Bir ürün daha ekleyelim
        if (err) return console.error(err);
        siparisiOnayla([sepetUrun, sepetUrun2], (err, id) => {
          if (err) return console.error(err);
          console.log(Mutlu son! Sipariş ID: ${id});
        });
      });
    });
  });
});

Yukarıdaki kod bloğu, birden fazla iç içe callback'in nasıl bir "piramit" veya "merdiven" yapısı oluşturduğunu açıkça göstermektedir. Bu yapı, hem okunaklılığı düşürür hem de hata yönetimini (her adımda if (err) return... kontrolü) karmaşıklaştırır. İşte bu noktada modern JavaScript, Promise'ler ve Async/Await ile devreye girerek bu karmaşıklığı çözmek için daha zarif ve güçlü yöntemler sunar.

Promise'lerin Kurtarıcı Rolü: Yemek Siparişinizin Geleceğine Dair Bir Taahhüt

Callback cehenneminin getirdiği zorluklar, JavaScript topluluğunu daha iyi bir çözüm aramaya itti ve böylece Promise'ler (Vaatler) ortaya çıktı. Promise'ler, gelecekte tamamlanacak veya başarısız olacak bir işlemin nihai sonucunu temsil eden nesnelerdir. Onları, bir yemek siparişi verdiğinizde size verilen bir "taahhüt" veya "sipariş fişi" gibi düşünebilirsiniz.

Sipariş Fişiniz: Promise'in Üç Hali

Swiggy veya Zomato'dan bir sipariş verdiğinizde, uygulama size hemen yemeğinizin masanıza gelmesini sağlamaz. Bunun yerine, bir "sipariş fişi" verir (uygulamadaki sipariş durumunuz gibi). Bu fişin üç olası durumu vardır:

  1. Beklemede (Pending): Siparişiniz mutfakta hazırlanıyor. Henüz tamamlanmadı veya iptal edilmedi. (new Promise() ile oluşturulan başlangıç durumu).
  2. Yerine Getirildi (Fulfilled/Resolved): Yemeğiniz hazırlandı ve teslim edildi. Her şey yolunda gitti. (Promise'in başarılı bir şekilde tamamlanması).
  3. Reddedildi (Rejected): Yemeğiniz hazırlanamadı veya siparişiniz iptal edildi (örneğin, mutfakta malzeme kalmadı). (Promise'in bir hatayla sonuçlanması).

Promise'ler sayesinde, bir asenkron işlemin sonucunu hemen beklemek yerine, o işlemin gelecekteki durumuna dair bir "söz" almış olursunuz. Bu sözün yerine getirilmesini veya reddedilmesini takip edebilir, buna göre hareket edebilirsiniz.

Promise'ler Nasıl Kullanılır? Zincirleme ve Hata Yönetimi

Promise'ler, .then(), .catch() ve .finally() metodları aracılığıyla kullanılır. Bu metodlar, Promise'in durumuna göre hangi kodun çalışacağını belirtmenizi sağlar ve en önemlisi, birbirlerine zincirlenebilirler, bu da callback cehennemini ortadan kaldırır.

  • .then(onFulfilled, onRejected): Promise başarıyla tamamlandığında (onFulfilled) veya reddedildiğinde (onRejected) çalışacak fonksiyonları tanımlar. Genellikle onRejected için ayrı bir .catch() kullanılır.
  • .catch(onRejected): Promise reddedildiğinde (bir hata oluştuğunda) çalışacak fonksiyonu tanımlar. Hata yönetimini merkezi hale getirir.
  • .finally(onFinally): Promise'in durumu ne olursa olsun (başarılı veya başarısız), her zaman çalışacak fonksiyonu tanımlar. Genellikle temizleme işlemleri için kullanılır (örneğin, yükleme animasyonunu kapatmak).

Callback cehennemi örneğini Promise'ler ile yeniden yazalım:


function restoranSecPromise() {
  console.log("Restoranlar yükleniyor (Promise)...");
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const restoran = "Burgercü Baba";
      console.log(${restoran} seçildi.);
      resolve(restoran); // Başarılı, restoranı geri döndür
    }, 1000);
  });
}

function menuyuGetirPromise(restoran) {
  console.log(${restoran} menüsü yükleniyor (Promise)...);
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const menu = ["Hamburger", "Patates", "Kola"];
      console.log("Menü yüklendi.");
      resolve(menu);
    }, 1500);
  });
}

function sepeteEklePromise(menu, urun) {
  console.log(${urun} sepete ekleniyor (Promise)...);
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (menu.includes(urun)) {
        console.log(${urun} sepete eklendi.);
        resolve({ urun, miktar: 1 });
      } else {
        reject(Ürün "${urun}" menüde yok!); // Hata
      }
    }, 800);
  });
}

function siparisiOnaylaPromise(sepet) {
  console.log("Sipariş onaylanıyor (Promise)...");
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const siparisID = Math.floor(Math.random() * 10000);
      console.log(Siparişiniz ${siparisID} ile onaylandı!);
      resolve(siparisID);
    }, 2000);
  });
}

// Promise zincirlemesi ile daha temiz kod:
restoranSecPromise()
  .then(restoran => menuyuGetirPromise(restoran)) // Restoran seçilince menüyü getir
  .then(menu => Promise.all([ // Birden fazla ürünü aynı anda sepete ekle (ileride detaylandırılacak)
    sepeteEklePromise(menu, "Hamburger"),
    sepeteEklePromise(menu, "Patates")
  ]))
  .then(sepetUrunleri => siparisiOnaylaPromise(sepetUrunleri)) // Sepet ürünleri gelince siparişi onayla
  .then(siparisID => console.log(Mutlu son! Sipariş ID: ${siparisID})) // Sipariş onaylanınca ID'yi göster
  .catch(error => console.error("Bir hata oluştu:", error)) // Herhangi bir adımda hata olursa yakala
  .finally(() => console.log("Sipariş akışı tamamlandı.")); // Her durumda çalış

Gördüğünüz gibi, Promise'lerle kod daha yatay ve okunabilir hale geldi. Her .then() bloğu, bir önceki Promise'in başarılı sonucunu alır ve bir sonraki asenkron işlemi başlatır. Zincirin herhangi bir noktasında bir hata oluşursa, kontrol hemen en yakın .catch() bloğuna atlar, bu da hata yönetimini çok daha kolay ve merkezi hale getirir. Artık, Swiggy'de bir siparişin birden fazla aşamadan geçerken uygulamanın nasıl bu kadar akıcı kaldığını daha iyi anlıyorsunuz. Promise'ler sayesinde her adım bir sonrakine söz vererek ilerler ve kullanıcı deneyimi kesintisiz olur.

Async/Await: Yemek Hazırlanırken Mutfakta Beklemek Gibi mi?

Promise'ler, callback cehennemine önemli bir çözüm getirmiş olsa da, özellikle uzun Promise zincirlerinde yine de okunabilirlik ve kodun akışı konusunda bazı zorluklar yaşanabilirdi. İşte bu noktada JavaScript'e ECMAScript 2017 (ES8) ile eklenen async/await anahtar kelimeleri devreye girdi. Async/Await, Promise'ler üzerine kurulu "sentaktik bir şeker" olup, asenkron kodu senkron kod gibi, çok daha temiz ve okunaklı bir şekilde yazmamızı sağlar.

Mutfakta "Bekleyen" Aşçı, Çalışan Restoran

Async/Await'i anlamak için yine restoran analogisine dönelim. Aşçı yemeği hazırlarken bazen belirli bir malzemenin hazır olmasını beklemek zorundadır (örneğin, fırındaki kekin pişmesini beklemek gibi). Bu "bekleme" (await) anında aşçı o anki görevi için durur, ancak restoranın geri kalanı (garsonlar, diğer aşçılar) çalışmaya devam eder. Yani, aşçı belirli bir işi "beklerken" ana mutfak (JavaScript'in ana iş parçacığı) bloke olmaz, diğer işlere devam edebilir. Ancak, aşçının kendisi o an için "bekleme" durumundadır.

async Fonksiyonlar ve await Anahtar Kelimesi

async kelimesi bir fonksiyonun önüne yazıldığında, o fonksiyonun her zaman bir Promise döndüreceğini belirtir. Bu fonksiyonun içinde await anahtar kelimesini kullanabiliriz. await ise sadece async fonksiyonlar içinde kullanılabilir ve bir Promise'in tamamlanmasını bekler. Promise yerine getirildiğinde (resolved), await bize Promise'in değerini döndürür; eğer reddedilirse (rejected), bir hata fırlatır.


// Promise'leri döndüren fonksiyonlarımız zaten mevcut:
// restoranSecPromise(), menuyuGetirPromise(), sepeteEklePromise(), siparisiOnaylaPromise()

async function siparisVerAsync() {
  console.log("--- Async/Await ile Sipariş Verme ---");
  try {
    const restoran = await restoranSecPromise(); // Restoran seçilmesini bekle
    const menu = await menuyuGetirPromise(restoran); // Menünün gelmesini bekle
    
    // Birden fazla ürünü paralel olarak sepete ekle (Promise.all ile)
    const sepetUrunleri = await Promise.all([
      sepeteEklePromise(menu, "Hamburger"),
      sepeteEklePromise(menu, "Patates")
    ]);

    const siparisID = await siparisiOnaylaPromise(sepetUrunleri); // Siparişin onaylanmasını bekle
    console.log(Sonuç: Async/Await ile sipariş ID: ${siparisID});
  } catch (error) {
    console.error("Async/Await hatası:", error); // Hata yakalama
  } finally {
    console.log("Async/Await sipariş akışı tamamlandı.");
  }
}

siparisVerAsync();

Yukarıdaki kodu incelediğimizde, asenkron işlemlerin sanki senkron bir şekilde, adım adım yazılmış gibi göründüğünü fark edeceksiniz. await sayesinde, bir Promise tamamlanana kadar kodun o satırda "duraklamasını" sağlayabiliriz. Ancak unutulmamalıdır ki bu "duraklama", sadece async fonksiyonun kendi içindeki yürütmeyi durdurur, JavaScript'in ana iş parçacığını bloke etmez. Ana iş parçacığı, await edilen Promise'in tamamlanmasını beklerken diğer işleri yapmaya devam eder. Promise çözümlendiğinde veya reddedildiğinde, async fonksiyon kaldığı yerden devam eder.

Hata yönetimi try...catch blokları ile Promise'lerden bile daha tanıdık ve anlaşılır bir şekilde yapılabilir, tıpkı senkron kodlarda olduğu gibi. .finally() bloğu da aynı işlevi görür ve işlem başarılı olsun ya da olmasın çalışır.

async/await kullanımı, özellikle Swiggy ve Zomato gibi karmaşık etkileşimlerin olduğu uygulamalarda kodun bakımını, test edilebilirliğini ve genel okunabilirliğini muazzam derecede artırır. Restoran listesinin yüklenmesi, filtreleme uygulanması, siparişin ödeme aşamasına geçilmesi gibi birçok adımın sorunsuz bir şekilde birbirini takip etmesi, büyük ölçüde bu modern asenkron programlama deseni sayesinde mümkün olmaktadır.

Kısacası, async/await ile asenkronluğun karmaşıklığını soyutlayarak, adeta "mutfakta bekliyormuş gibi" ama aslında tüm restoranın akışını bozmadan, akıcı bir kullanıcı deneyimi sunmak artık çok daha kolay.

Uzman İpucu: async fonksiyonlar her zaman bir Promise döndürür. Eğer async bir fonksiyon içinde açıkça bir değer döndürmezseniz, varsayılan olarak Promise.resolve(undefined) döndürür. Bu, async fonksiyonları diğer Promise tabanlı kodlarla kolayca entegre etmenizi sağlar.

Performans Canavarı: Swiggy ve Zomato Nasıl Takılmadan Çalışıyor?

Swiggy, Zomato ve benzeri mobil odaklı platformların başarısının anahtarlarından biri, kullanıcıya sundukları kesintisiz ve hızlı deneyimdir. Asenkron JavaScript, bu akıcı deneyimin temelini oluşturur. Peki, asenkronluk tam olarak nasıl bir "performans canavarı"na dönüşüyor ve mobil uygulamalardaki takılmaları nasıl önlüyor?

API Çağrıları ve Arayüz Akıcılığı

Bir yemek siparişi uygulamasında, arka uç sunucularından sürekli olarak veri çekilir: restoran listeleri, menüler, kullanıcı yorumları, teslimat bölgeleri, promosyonlar ve daha fazlası. Bu veri çekme işlemleri (API çağrıları), ağ gecikmelerine tabidir ve genellikle birkaç yüz milisaniye hatta saniyeler sürebilir. Eğer bu çağrılar senkron olsaydı, her API isteği gönderildiğinde uygulama tamamen donar, kullanıcı ekranda hiçbir şey yapamazdı. Bu durum, mobil kullanıcılar için kabul edilemez bir deneyim olurdu.

Asenkron JavaScript sayesinde, uygulamanız bir restoran listesi için API çağrısı yaptığında, bu işlem arka plana atılır ve ana iş parçacığı serbest kalır. Bu, kullanıcının bu süreçte menüler arasında gezinebileceği, filtreleri ayarlayabileceği veya konumunu güncelleyebileceği anlamına gelir. API'den veri geldiğinde, asenkron mekanizmalar sayesinde uygulama verileri işler ve kullanıcı arayüzünü günceller, tüm bunlar kullanıcının hissedeceği bir takılma olmadan gerçekleşir.

Görsel Yüklemeler ve Kullanıcı Deneyimi

Swiggy ve Zomato, görselliğe dayalı uygulamalardır. Restoran fotoğrafları, yemek görselleri, banner'lar gibi yüksek çözünürlüklü birçok resim yüklenir. Bu resimlerin indirilmesi ve belleğe yüklenmesi de zaman alıcı asenkron bir işlemdir. Asenkron yükleme sayesinde, resimler birer birer veya topluca arka planda indirilirken, kullanıcının ekranda diğer içerikleri görmesi veya uygulamanın geri kalanıyla etkileşim kurması engellenmez. Yüklenen resimler hazır oldukça ekranda belirir, bu da kullanıcının uygulamanın "canlı" ve tepkisel olduğu hissini güçlendirir.

Mobil Uyumlu Tasarım ve Performans

Mobil cihazlar, farklı ekran boyutlarına ve ağ koşullarına sahiptir. Asenkron JS, bu farklılıkları yönetmeye yardımcı olur. Örneğin, düşük bant genişliğine sahip bir kullanıcı için resimlerin daha düşük kalitede yüklenmesi veya verilerin daha küçük paketler halinde alınması gibi stratejiler asenkron işlemlerle entegre edilebilir. Ayrıca, arayüzün mobil cihazlarda düzgün görünmesi için HTML ve CSS'in duyarlı olması gerekir. Asenkron işlemler arayüzün akıcılığını sağlarken, duyarlı tasarım da görünümün doğru olmasını garantiler.





    
    
    Mobil Yemek Uygulaması
    


    

Yakındaki Restoranlar

Yukarıdaki HTML ve JavaScript kodunda, fetchRestaurants fonksiyonu asenkron olarak restoran verilerini çekiyor. Bu işlem sırasında kullanıcı "Restoranlar yükleniyor..." mesajını görür. Veriler geldiğinde, her bir restoran kartı dinamik olarak oluşturulur ve hafif bir animasyonla ekrana eklenir. Bu, kullanıcının uygulamanın yanıt verdiğini hissetmesini sağlar ve tüm liste yüklenene kadar donmasını engeller. CSS'deki @media sorgusu ise mobil cihazlarda kartların dikey hizalanmasını sağlayarak duyarlı bir tasarım örneği sunar.

Sonuç olarak, asenkron JavaScript, modern Swiggy ve Zomato gibi uygulamaların hızlı, akıcı ve kullanıcı dostu olmasının temel taşıdır. Arka planda karmaşık veri işlemlerini yönetirken bile, kullanıcı arayüzünü her zaman tepkisel tutarak üstün bir kullanıcı deneyimi sunar.

Daha Akıllı Asenkron Yönetim: Birden Çok Siparişi Aynı Anda Nasıl İdare Edersiniz?

Modern uygulamalarda sadece tek bir asenkron işlemi yönetmek yeterli değildir. Genellikle birden fazla asenkron işlemin aynı anda başlatılması, bunların sonuçlarının beklenmesi veya belirli bir koşulun gerçekleşmesi durumunda harekete geçilmesi gerekir. JavaScript'in Promise API'si, bu tür senaryolar için güçlü araçlar sunar: Promise.all() ve Promise.race().

Promise.all(): Tüm Yemeklerin Aynı Anda Hazır Olmasını Beklemek

Bir aile yemeği siparişi verdiğinizi düşünün. Masadaki herkesin yemeği aynı anda gelirse en iyi deneyim yaşanır. Hiç kimse kendi yemeği gelmeden diğerlerini beklemek veya yemeği soğumuşken diğerlerinin gelmesini seyretmek istemez. İşte Promise.all() tam da bu senaryoya karşılık gelir. Bir dizi Promise'i alır ve bu Promise'lerin tümü başarılı bir şekilde tamamlanana kadar bekler. Tüm Promise'ler yerine getirildiğinde, Promise.all() kendi sonucunu, Promise'lerin sonuçlarının bir dizisi olarak döndürür (Promise dizisindeki sırayla). Eğer Promise'lerden herhangi biri reddedilirse, Promise.all() hemen reddedilir ve ilk reddedilen Promise'in hatasını döndürür.

Kullanım Alanı:

  • Bir sayfa yüklenmeden önce birden fazla API'den veri çekmek (örneğin, kullanıcı profili, bildirimler ve ayarlar).
  • Birden fazla görseli aynı anda yüklemek ve tümü hazır olduğunda göstermek.
  • Swiggy'de bir siparişin farklı aşamalarını aynı anda kontrol etmek (örneğin, restoran onayı, aşçı onayı, kurye ataması).

// Promise'leri döndüren örnek fonksiyonlarımız:
// restoranSecPromise(), menuyuGetirPromise(), sepeteEklePromise(), siparisiOnaylaPromise()

async function topluSiparisVer() {
  console.log("--- Promise.all ile Toplu Sipariş ---");
  try {
    const restoran = await restoranSecPromise();
    const menu = await menuyuGetirPromise(restoran);

    console.log("Birden fazla ürünü sepete paralel ekliyorum...");
    const [hamburger, patates, kola] = await Promise.all([
      sepeteEklePromise(menu, "Hamburger"),
      sepeteEklePromise(menu, "Patates"),
      sepeteEklePromise(menu, "Kola")
    ]);

    console.log("Tüm ürünler sepete eklendi:", [hamburger, patates, kola]);
    const siparisID = await siparisiOnaylaPromise([hamburger, patates, kola]);
    console.log(Toplu siparişiniz başarıyla oluşturuldu! ID: ${siparisID});

  } catch (error) {
    console.error("Toplu sipariş sırasında hata:", error);
  }
}

topluSiparisVer();

Yukarıdaki örnekte, sepeteEklePromise çağrıları Promise.all() içine alındığı için, bu üç işlem (Hamburger, Patates, Kola ekleme) eş zamanlı olarak başlar ve hepsi tamamlandığında bir sonraki adıma (siparisiOnaylaPromise) geçilir. Bu, toplam bekleme süresini önemli ölçüde azaltır.

Promise.race(): En Hızlı Hazır Olan Yemeği Yemek

Bazen birden fazla asenkron işlem başlatırız ama sadece ilk tamamlanan sonuca ihtiyacımız vardır. Örneğin, aynı bilgiyi farklı sunuculardan çekmeye çalışırken, hangi sunucunun daha hızlı yanıt verdiğini öğrenmek isteyebiliriz. Ya da bir yemek uygulamasında, birden fazla restorandan aynı yemeği sipariş ettiğimizi ve hangisinin ilk olarak siparişi onayladığını bilmek istediğimizi düşünelim. İşte bu senaryoda Promise.race() devreye girer. Bir dizi Promise alır ve bu Promise'lerden ilk tamamlanan (başarılı ya da başarısız) Promise'in sonucunu döndürür.

Kullanım Alanı:

  • Birden fazla API'den veri çekmeye çalışırken en hızlı yanıt vereni kullanmak.
  • Bir işlemin belirli bir süre içinde tamamlanıp tamamlanmadığını kontrol etmek (timeout eklemek).
  • Birden fazla kaynaktan gelen verilerden en güncel olanı almak.

async function enHizliServis() {
  console.log("--- Promise.race ile En Hızlı Servis ---");

  // Farklı restoranlardan aynı yemeği sipariş ettiğimizi varsayalım
  const siparis1 = new Promise(resolve => setTimeout(() => resolve("Restoran A: Pizza hazır!"), 3000)); // 3 saniye
  const siparis2 = new Promise(resolve => setTimeout(() => resolve("Restoran B: Pizza hazır!"), 1500)); // 1.5 saniye
  const siparis3 = new Promise(resolve => setTimeout(() => resolve("Restoran C: Pizza hazır!"), 2000)); // 2 saniye

  try {
    const ilkGelen = await Promise.race([siparis1, siparis2, siparis3]);
    console.log(İlk gelen servis: ${ilkGelen});
  } catch (error) {
    console.error("Servislerde hata:", error);
  }
}

enHizliServis();

Bu örnekte, Promise.race() çağrıldığında, üç Promise de aynı anda başlar. siparis2 en kısa sürede (1.5 saniye) tamamlandığı için, ilkGelen değişkenine onun sonucu atanır ve diğer Promise'lerin sonuçları artık önemsiz hale gelir.

Hem Promise.all() hem de Promise.race(), özellikle Swiggy ve Zomato gibi birden fazla veri kaynağını veya işlemi aynı anda yönetmesi gereken karmaşık uygulamalarda, asenkron kodu daha verimli ve anlamlı hale getiren güçlü araçlardır. Bu sayede uygulamalar, birden fazla işlemi paralel olarak yürüterek genel performansı artırabilir ve kullanıcılara daha hızlı geri bildirim sağlayabilir.

Sonuç: Asenkron JavaScript ile Akıcı Bir Deneyim Mümkün mü?

Geliştirdiğimiz her web veya mobil uygulama için kullanıcı deneyimi kritik öneme sahiptir. Swiggy, Zomato ve benzeri platformlar, kullanıcıların parmaklarının ucunda anlık geri bildirim ve kesintisiz bir akış beklediği dijital çağda bu beklentiyi fazlasıyla karşılıyor. İşte bu akıcı deneyimin arkasında yatan sihirli değnek, asenkron JavaScript'in gücüdür.

Bu makalede, senkronizasyonun blokaj yaratan doğasından başlayıp, callback fonksiyonlarının getirdiği ilk asenkron çözümü ve onun zorlukları olan "callback cehennemini" inceledik. Ardından, Promise'lerin bu zorlukları nasıl ele aldığını ve asenkron kodumuzu daha yönetilebilir hale getirdiğini gördük. Son olarak, async/await ile asenkron programlamanın ne kadar temiz, okunaklı ve senkron bir akışta yazılabildiğini keşfettik.

Swiggy'den bir sipariş verirken restoranların listelenmesi, menünün yüklenmesi, ürünlerin sepete eklenmesi ve ödeme işleminin tamamlanması gibi her adımın, kullanıcının hiçbir zaman bir donma yaşamadan gerçekleşmesi, JavaScript'in Event Loop'u ve Promise/Async-Await gibi asenkron mekanizmalarının ustaca kullanımı sayesinde mümkün olmaktadır. Promise.all() ve Promise.race() gibi gelişmiş teknikler ise birden fazla asenkron işlemin daha akıllıca yönetilmesini sağlayarak performansı daha da artırır.

Modern bir geliştirici olarak, asenkron JavaScript'i anlamak ve etkili bir şekilde kullanmak artık bir seçenek değil, bir zorunluluktur. Bu beceri seti, hem kullanıcıların beklentilerini karşılayan hem de geliştiriciler için daha sürdürülebilir kod tabanları oluşturan uygulamalar inşa etmenizi sağlayacaktır. Unutmayın, kodunuzun mutfağı ne kadar iyi organize edilirse, servisiniz de o kadar hızlı ve kusursuz olacaktır!

Sıkça Sorulan Sorular

Async/await neden Promise'lerden daha iyi kabul edilir?

async/await, aslında Promise'ler üzerine inşa edilmiş sentaktik bir şekerdir. Bu da demek oluyor ki, async/await'in yaptığı her şey Promise'lerle de yapılabilir. Ancak, async/await kodu senkron kod gibi daha düz bir yapıda yazmanıza olanak tanır, bu da onu özellikle karmaşık ve zincirleme asenkron işlemler için çok daha okunaklı, anlaşılır ve bakımı kolay hale getirir. Hata yönetimi de try...catch bloklarıyla senkron kodlara benzer bir şekilde yapılabildiği için daha tanıdıktır.

JavaScript neden tek iş parçacıklı (single-threaded) bir dil olarak tasarlanmıştır?

JavaScript başlangıçta web tarayıcılarında DOM (Belge Nesne Modeli) manipülasyonu için tasarlanmıştır. Eğer birden fazla iş parçacığı aynı DOM öğesini aynı anda değiştirmeye çalışsaydı, bu tutarsızlıklara ve karmaşık eşzamanlılık sorunlarına yol açabilirdi. Tek iş parçacıklı bir yapı, bu tür karmaşıklıkları önleyerek geliştiricilerin işini kolaylaştırmıştır. Asenkron mekanizmalar (Event Loop, Callback Queue vb.) sayesinde, tek iş parçacıklı olmasına rağmen bloklama yapmadan eş zamansız görevleri başarıyla yönetebilir.

Asenkron kodda hata yönetimi nasıl yapılır?

Callback'lerde hata yönetimi genellikle callback fonksiyonuna ilk parametre olarak bir Error nesnesi göndermek (Node.js stilinde (err, data) => {...}) ve her adımda kontrol etmekle yapılır. Promise'lerde ise hatalar .catch() metodu ile merkezi olarak yakalanır. async/await kullanırken, hata yönetimi en kolay ve anlaşılır şekilde try...catch blokları ile yapılır, tıpkı senkron kodlarda olduğu gibi.

JavaScript Event Loop nedir ve asenkronluğu nasıl sağlar?

Event Loop, JavaScript'in tek iş parçacıklı doğasına rağmen asenkron işlemleri yönetmesini sağlayan temel bir mekanizmadır. Ana iş parçacığı (Call Stack), senkron kodları yürütür. Asenkron bir işlem (örneğin, bir ağ isteği veya setTimeout) başlatıldığında, bu işlem arka plana (Web API'leri, Node.js API'leri gibi) gönderilir. Arka plandaki işlem tamamlandığında, bir "callback" veya "Promise çözümü" görevi, bir kuyruğa (Callback Queue veya Microtask Queue) eklenir. Event Loop sürekli olarak Call Stack'i kontrol eder; eğer boşsa, kuyruklardan bir sonraki görevi alır ve Call Stack'e iter, böylece bu görev ana iş parçacığında yürütülebilir.

Callback'lerin dezavantajları callback hell ile mi sınırlıdır?

Callback hell (iç içe geçmiş callback'ler) callback'lerin en bilinen dezavantajı olsa da, başka sorunları da vardır:

  1. Hata Yönetimi Zorluğu: Hatalar, callback zincirinin her seviyesinde ayrı ayrı ele alınması gerektiğinden karmaşıklaşır.
  2. Inversion of Control (Kontrolün Tersine Dönmesi): Bir fonksiyonun, parametre olarak aldığı callback'i ne zaman ve kaç kez çağıracağını kontrol edememesi durumudur. Bu, beklenmedik davranışlara yol açabilir.
  3. Okunabilirlik ve Bakım Zorluğu: Kodun derinliği arttıkça anlaşılması ve hataların ayıklanması güçleşir.

Promise'ler ve async/await bu sorunların çoğuna daha zarif ve yönetilebilir çözümler sunar.

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.
Exit mobile version