Gelecegi yazanlar logo

Bloga geri dön

Javascript

JavaScript İle Eşzamansız Programlama | Geleceği Yazanlar

JavaScript’te eşzamansız programlama ile modern ve ölçeklenebilir akışlar kurun. Pratik örnekleri Geleceği Yazanlar Blog’da hemen keşfedin!
Blog image

Eşzamansız programlama (asynchronous programming), bir programın potansiyel olarak uzun sürecek bir görevi başlatmasını ve bu görev tamamlanana kadar beklemek yerine, diğer olaylara yanıt verebilmesini sağlayan bir tekniktir. Yani, bir olay gerçekleşirken başka olayları bekleme sırasına almayıp, işleme alarak programı kesintisiz çalışır hale getirmektir.


Modern uygulamalar, kullanıcılara sorunsuz ve akıcı bir deneyim sunma hedefindedirler. Senkron (eş zamanlı) çalışan bir programlama tekniğinde, bir işlem başladığında program akışı durur ve o işlemin bitmesini beklenir. Basit bir web uygulamasında bile kullanıcıyı rahatsız edecek bu durum, karmaşık uygulamalarda ise kabul edilemez bir kullanıcı deneyimine yol açar. Bu durumu, günümüzde kullanılan pek çok eş zamansız işlemlerin olduğu karmaşık programlar düşünüldüğünde çok zorlayıcı olabilir. Eş zamansız çalışan programlar için mobilde uygulamalardan API ile veri çekilirken veya veritabanı sorgulaması yapılırken kullanılan işlemler; masaüstü uygulamalarda örnek olarak bir fotoğraf düzenleme uygulamasında büyük bir resim işleme veya bir raporlama uygulamasında büyük bir veri setini hesaplanması; oyunlarda, seviye yükleme veya ağdan veri çekme gibi işlemler; sunucu tarafı uygulamalarında (Node.js gibi) veritabanından veri çekme, dosya okuma/yazma veya dış bir API'ye istek atma gibi işlemler örnek gösterilebilir. Bu işlemler gerçekleşirken eş zamansız programlama yöntemleriyle kullanıcıya hiçbir donma veya kopukluk hissi yaşatılmaz.

Sıralı işlem yapmaya izin veren eş zamanlı programlamanın aksine, eşzamansız programlamanın en klasik benzetmesi şudur: Bir mutfakta aynı anda birden fazla yemeğin pişirileceğini düşünelim. Örneğin, fırındaki kekin pişmesi beklenirken, bir yandan ana yemeğin hazırlanmasına devam edilebilir. Mesela, makarna için su ısıtılırken fırın ısınması için çalıştırılabilir, kekin malzemeleri karıştırılabilir. Yazılımda da benzer şekilde, eşzamansız bir işlem başlatıldığında (ekrana bir bilgi çekilirken, yükleniyor yazısı gibi), ana program akışı kilitlenmez ve kullanıcı arayüzü etkileşime açık kalır. İşlem tamamlandığında, önceden tanımlanmış bir fonksiyon aracılığıyla sonuçlar işlenir. Bu, kullanıcının arka planda işlemlerin yürütülmesinden bağımsız kendi işine devam edebilmesini sağlar.


Modern Yöntemlerden Önce

Farklı yazılım dillerinde eşzamansız programlamanın yerini alacak farklı kullanımlar vardır. Bu uygulamalardan biri JavaScript’te zamanlayıcının süresi dolduğunda bir fonksiyonu veya belirtilen kod parçasını çalıştıran bir zamanlayıcı ayarlamaya yarayan Window arayüzünün setTimeout() yöntemidir. Bu yöntemle, bir fonksiyon, belirli bir gecikme süresi sonunda bir kez çalıştırma için yerleşik (built-in) olarak kurulabilir. Aşağıda 5 saniyelik bir süre sonrası selamlama işlevi yazılmıştır.




JavaScript'te eşzamansız programlama için ilk ve yaygın kullanılan fonksiyon callback fonksiyonlarıdır. Callback, ana fonksiyonun işi bittiğinde çağrılır ve bir işin bitmesini beklemek gibi bir kesinti yaşanmasının önüne geçer. Bu sebeple, ilgili fonksiyona argüman olarak verilir. Basitçe fonksiyona "bana bu işi yap, bittiğinde bu fonksiyonu çağır" dediğimiz, setTimeout gibi zamanlayıcılardan, bir sunucudan veri çekmeye kadar birçok eşzamansız işlemde kullanılır. Bu yapıyla, programın ana akışı engellenmeden arka planda işlem yapılabilir

Her ne kadar bu yapı kullanışlı olsa da temel sorunu projenin büyüklüğüyle pratikliğinin orantılı gitmemesidir. Birden fazla (büyük programlarda çok fazla) eşzamansız işleri arka arkaya çağırmak gerektiğinde kodlar iç içe geçerek “Callback Hell” (Geri çağırma cehennemi) de denilen piramit benzeri bir yapıya döner. Bu yapıların iç içe geçmesi zamanla kodun okunabilirliğini ve temizliğini bozar. Her fonksiyonun kendi hata ayıklamasını yapması gerektiğinden, kodlara bir de hata yönetimi eklendiğinde iş daha da karmaşıklaşır.

Bir veri çekme fonksiyonundan örnek verelim. Bu örnekte ekrana sıralanmış kurslar ve blog yazıları listelenir.:



Burada API’lerden  kurs ve blog içeriği çağırma işlemi simüle edilmekte. fetchData fonksiyonu XHR (XMLHttpRequest) ile istek atar. Sonuç geldiğinde callback(err, data) çağrılır. Ardından, fetchDefault ile önce kursları çekip, sonra blog içeriklerini, sonra her ikisini birden callback’e geri iletir.

Görüldüğü gibi, bu yapılar bir piramit gibi yığılarak büyür ve karmaşıklaşır. Bundan uzak durabilmek için JavaScript (ve tabii kendilerine has fonksiyon, yöntem ve yapılarıyla diğer diller) belli çözümler sunmaktadır.


Callback Hell İçin Çözümler

Callback Hell ve yığılan kodlar gittikçe kod yazımını, okunmasını, hata yönetimini zorlaştırıp, işlevselliği düşürürken ortaya atılan ilk büyük ve başarılı çözüm Promise (söz) nesnesi oldu.


İlk Çözüm: Promise


Promise, eşzamansız bir işlemin sonuç itibariyle tamamlanmasını veya başarısız olmasını temsil eden bir nesnedir. Bir işlemi başlatır başlatmaz bir Promise nesnesi döner. Bu söz, işlemin ya başarılı olacağını (resolve) ya da başarısız olacağını (reject) söyler. Callback piramidinin aksine zincirleme bir yapıya izin verir ve .then() .catch() yöntemleriyle hata yönetimi merkezi bir yerden ele alınabilir. Aynı çağırma örneği üzerinden gidecek olursak, bu sefer kodlar bu şekilde düzenlenecektir:



İlk Promise.all ile iki fetch’in cevabını beklenir. Sonra gelen response’lar tekrar Promise.all ile JSON’a çevrilir. Son .then aşamasında set işlemi yapılır. Burada da .then().catch() zincirleri proje büyüdükçe okunabilirlik sorunu yaratabilir.



Bunun için de daha modern bir yöntem var: 


async / await.


Promiseler ile çalışan özel bir yapı olan async ve await yapısı kodun eşzamansız olmasına rağmen sanki eş zamanlıymış gibi okunmasını sağlayan bir sözdizimi kolaylığıdır (syntactic sugar [kolaylık tatlı gelir]). Bu yapıda, async, bir fonksiyonun bir Promise döndürmesini sağlarken, await bir fonksiyonun Promise için beklemesini sağlar. Çok benzer bu iki yapının bariz farkları vardır: Promise, eşzamansız işlemi yöneten bir nesne, async/await ise, bu Promise'leri daha rahat kullanmak için geliştirilmiş bir paradigmadır. Kodun daha sade, daha okunabilir olmasını ve try/catch blokları ile daha kolay yönetilebilir olmasını sağlar. Buradaki try/catch blokları .try().catch() zincirlerinden farklı olarak kodun eş zamanlıymış gibi görünmesini sağlar. Ayrıca uzun zincirlemeler sonucu oluşabilecek okuma zorluğunu da azaltır. Zincirde .catch() sadece kendi içindeki hatalar yakalanırken, /catch bloğunda await öncesi/sonrası senkron hataları da aynı blokta yakalanır.

Farkı daha iyi anlamak için yazının başından beri kullanılan veri çekme örneğine bakalım:



API ile çağrılan veriler async/await yapısıyla daha sade ve verimli hale getirilir. Promise.all(... satırı ile birden fazla Promise aynı anda başlatılır ve hepsi tamamlandığında sonuçlar bir dizi olarak döner. Bu, diğer kullanımlara kıyasla async/await kullanımının en büyük farkıdır. Bu özellik ayrıca hız olarak da avantaj sağlar. Geleneksel fetch yapısında .then() metotlarıyla zincirleme gerekirken, burada kod satır satır ilerler. Önce tüm yanıtlar beklenir, sonra yanıtların başarılı olup olmadığı kontrol edilir, ardından her bir yanıt json() formatına dönüştürülmek üzere ayrı ayrı beklenir. Kod, bir hikaye gibi yukarıdan aşağıya doğru okunur, bu da mantıksal akışı takip etmeyi oldukça kolaylaştırır. Tüm asenkron işlemlerde oluşabilecek hatalar (ağ hatası, sunucu hatası vb.) tek bir try...catch bloğu ile yönetilir.

Özet olarak, günümüzde neredeyse modern web uygulamalarının tamamı  async/await ile işlerken neden eş zamansız yönetilmesi gereken programların setTimeout ve callback sürecini geride bırakıp, modern yapıya geçildiğine bir bakalım:

  • Kod akışı, okunabilirliği ciddi ölçüde arttıracak şekilde, neredeyse eş zamanlı gibi görünür.
  • Hata yönetimi bloklar halinde daha düzenli hale getirilir.
  • Zincir halinde düşünmek yerine yukarıdan aşağı akan, ele alması ve okunması kolay bir yapı kurulur.
  • Kodun okunabilirliği, anlaşılırlığı ve sürdürülmesi kolaylaştığı için takım çalışmasına daha elverişli bir yapı ortaya çıkar.



Kaynakça

https://developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Async_JS/Introducing

https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout

https://www.w3schools.com/jsref/met_win_settimeout.asp

https://javascript.info/callbacks

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises

https://www.w3schools.com/js/js_async.asp

*Görseller Google Gemini ve Chat GPT kullanılarak üretilmiştir


Beyza Cemre DİŞLİ

|

18 Kasım 2025