Callback, Promise ve async/await, JavaScript'te asenkron kodu yönetmek için kullanılan üç yaklaşımdır. Callback bir fonksiyonu başka bir fonksiyona parametre olarak verir, Promise bir işlemin ileride başarılı veya başarısız sonuçlanacağını temsil eden bir nesne sunar, async/await ise Promise'i senkron görünen bir sentaksla kullanmanızı sağlar.
Konularımıza başlamadan önce asenkron ve senkron programlama arasındaki farkı kısa bir özetle açıklayalım.
Senkron programlamada bir fonksiyon bitmeden diğeri çalışmaya başlamaz; yani birbirlerini beklerler, bekleyen fonksiyonun cevabı dönmeden diğerine geçilmez. Asenkron fonksiyonlar ise aynı anda başlatılabilir ve birbirlerinin sonucunu beklemeden çalışmaya devam eder; hangisinin sonucu önce çıkarsa o sonuç önce görünür. JavaScript tek bir iş parçacığıyla (single-thread) çalışır, ama event loop (olay döngüsü) sayesinde asenkron işlemleri bloklamadan yönetir. Yani JavaScript bir satırı okuyup çıktısını beklemeden diğer satıra geçebilir; asenkron işlemleri bir kuyruğa alır ve sırası geldikçe çalıştırır.
Aşağıdaki tablo bu üç yaklaşımı kısaca karşılaştırır.
Yaklaşım | Çalışma mantığı | Başlıca dezavantajı |
Callback | Bir fonksiyonu başka bir fonksiyona parametre olarak verirsiniz; işlem bitince callback çalışır | İç içe çok sayıda callback kullanıldığında "callback hell" oluşur, kod okunmaz hale gelir |
Promise | Bir işlemin ileride başarılı (resolve) veya başarısız (reject) sonuçlanacağını temsil eden bir nesnedir | Zincirleme .then() kullanımı artınca okunabilirlik yine düşer |
Async/Await | async fonksiyon her zaman bir promise döner; await ile bu promise'in sonucunu senkronmuş gibi bekleyebilirsiniz | Sadece async fonksiyonlar içinde kullanılabilir; try/catch ile hata yönetimi ayrıca gerekir |
Callback, bir fonksiyonu başka bir fonksiyona parametre olarak verip kullanmaya denir. Bu şekilde kullanarak fonksiyonun belirli bir işlem bittikten sonra çalışmasını sağlayabiliriz.
Bir örnekle açıklamaya çalışalım.

Yukarıdaki örnekte gördüğünüz üzere "printAll" fonksiyonu çağırıldığında her zaman farklı bir sırada çıktı göreceğiz. Hiçbir çıktı birbirini beklemeyecek; süresi az olan hangisi olursa o önce çıkacaktır. Callback kullanarak bunun istediğimiz sırayla çıkmasını istersek, şu şekilde yapabiliriz.

Callback kullanmaktaki amacımız, önce hangi fonksiyonun çalışacağını ve sonucu ne zaman göstereceğini belirlemektir.
Fakat callback kullanımında birkaç problem vardır:
Yukarıdaki örnekte sıralayacağımız değer sayısı artsaydı, çok daha fazla iç içe callback yazmamız gerekecekti. Callback fonksiyonlardaki bu problemler yüzünden Promise kavramı oluşturuldu.
Promise, adının Türkçe karşılığından da anlaşılacağı üzere bir "söz"dür. Mesela internetten bir sipariş verdiğinizde, size bir sipariş sözü verilir; yani elinize bir mesaj veya mal geçecektir. Buna "promise" denir. Bildirimi beklemeye "pending", cevap başarılıysa yani sipariş size ulaşacaksa "resolved", siparişiniz iptal olduysa "rejected" denir. Başarısız durumda hata çözümleme (error handling) yapılarak başka bir çözüm yoluna gidilir.
Yukarıda yaptığımız callback örneğini bu kez Promise ile yapalım.

Burada oluşturduğumuz "printOrder" fonksiyonu bize bir Promise oluşturup döndürecek. Oluşturduğumuz Promise'teki if bloğu true ise resolve olacak ve then içinde çözülerek gönderdiğimiz order parametresini dönecek. Eğer false olursa reject olacak ve catch bloğunda yakalanıp konsolda gösterilecek.
Fakat callback fonksiyonlarda oluşan callback hell sorunu burada da oluşmaya başladı. Bunun çözümü için async/await fonksiyonlar kullanılmaya başlandı. Bu fonksiyonlar, geliştirici ve kodu okuyan insanlar için kodun asenkron olmasına rağmen senkron görünmesini sağlar.
Bu fonksiyonların amacı, birbirini beklemeyen asenkron fonksiyonları senkron gibi okunabilir hale getirmemizi sağlamaktır. Bir fonksiyon başarılı olduğunda başka bir fonksiyon çalıştırmak istediğimizde ve bu sırada bekleme yapmak istediğimizde bu yapıyı kullanırız. Mantık olarak Promise ile aynıdır, sadece yazımı daha anlaşılırdır.
Async/await fonksiyonu için bir örnek oluşturalım.

Yukarıdaki örnekte bir "deviceList" fonksiyonumuz var; bunu bir servise istek atıyormuş gibi düşünelim. Bir Promise'imiz var ve bu Promise'in içinde bir if bloğumuz var. Bu true olursa datalarımız 5 saniye içinde yüklenip resolve edilecek; tersi olursa data alımında bir sorun olacak ve reject edilecek.
Alt kısımda tanımladığımız "getDevices" fonksiyonu bir async fonksiyondur. Try/catch bloğumuz, await ettiğimiz fonksiyondan dönen cevap başarılıysa fonksiyonumuz konsola datamızı basacaktır. Başarısız olursa catch bloğuna düşüp "Datalar Alınamadı!!" hatasını göreceğiz. Await ettiğimiz kısımda datalar gelene, yani 5 saniye geçene kadar konsolda bir şey göremeyeceğiz; işlem sonuca vardığında ise önce datamızı, ardından "Datalar Alındı!!" mesajını göreceğiz.
Tek bir istekte yazımın nasıl olduğunu gördük. Şimdi de birkaç istek attığımız ve birden fazla await yazdığımız bir durumu düşünelim.


Yukarıda üç adet sahte istek fonksiyonu yazdık ve her birinin içinde bir Promise var. Bunların her birini async fonksiyonumuzun içinde çağırıp await verdik. Try/catch bloğumuz her birinin cevabını kontrol edecek; bir tanesi bile reject olursa onu catch'e düşürüp reject'ten gelen mesajı basacak. Buradaki örnekte çıktımız "reject2!" olacak.
Promise'lerin her biri resolve olsaydı ve try bloğunun içinde her sonucu konsola yazdırsaydık, her istek sırayla çalışıp yazdırdığımız sıraya göre çıktı alacaktık. Buna da bir örnek yapalım.

Burada konsolda göreceğimiz sonuç resolve2, resolve1, resolve3 sırasıyla olur, ama bu çıktıları görmek için yaklaşık 6 saniye bekleriz; çünkü tüm await'lerin çözülmesini ve bize başarılı dönmesini bekleriz.
Özetlemek gerekirse, callback, Promise ve async/await fonksiyonlarının tanımını ve nasıl çalıştıklarını anlatmaya çalıştık. Hepsi aslında asenkron çalışan JavaScript'i daha yönetilebilir hale getirmek için kullanılan yapılardır. Özellikle async/await yapısı, kodunuzu daha okunabilir hale getiren ve asenkron fonksiyonları basit bir şekilde senkron gibi gösteren bir yoldur.
Zorunlu değil, ama çoğu modern kodda önerilir. Basit, tek seferlik geri çağırmalarda callback yeterli olabilir; birden fazla asenkron adım birbirine bağlıysa Promise veya async/await daha okunaklı sonuç verir.
Hayır, async/await Promise üzerine kurulu bir sentakstır. Arka planda hala Promise çalışır; async/await sadece bu Promise'leri daha okunabilir bir şekilde yazmanızı sağlar.
Try/catch bloğu kullanılır. Await edilen Promise reject olursa, kontrol catch bloğuna geçer ve hatayı orada yakalarsınız.
Promise.all veya Promise.allSettled gibi yöntemleri kullanın. Bunlar birden fazla Promise'i paralel başlatıp hepsinin sonuçlanmasını tek seferde beklemenizi sağlar.
React'te asenkron veri çekme senaryolarını useEffect ile nasıl yöneteceğinizi merak ediyorsanız React Hooks Nedir ve Nasıl Kullanılır? yazımıza göz atabilirsiniz. Farklı bir dil öğrenmek isterseniz Go Programlama Diline Giriş yazımız iyi bir başlangıç noktasıdır.

Software Developer
Mailiniz başarıyla gönderilmiştir en kısa sürede sizinle iletişime geçilecektir.
Mesajınız ulaştırılamadı! Lütfen daha sonra tekrar deneyin.