Menu

JavaScript async/await Kullanımı: Asenkron Kodu Sadeleştirin

JavaScript'te async/await gerçekten nasıl çalışır? async fonksiyonlar, await ile promise bekleme, try/catch ile hata yönetimi ve Promise.all ile paralel çalıştırma.

async/await Aslında Gizlenmiş Promise'tir

JavaScript'teki async/await, yeni bir eşzamanlılık modeli değildir. Promise'lerin üzerine eklenmiş bir tatlandırıcıdır; asenkron olmasına rağmen sırayla çalışıyormuş gibi görünen kod yazmanı sağlar. Altta yatan mekanizma aynı, sadece yazım şekli çok daha dostça.

Aynı işi iki farklı şekilde yazalım:

index.js
Output
Click Run to see the output here.

Her iki fonksiyon da bir promise döndürüyor. İkisi de tamamen aynı işi yapıyor. async sürümü yukarıdan aşağıya okunuyor ve .then zincirine ihtiyaç duymuyor — bütün olay zaten bu.

async Bir Fonksiyonu Promise Döndürür Olarak İşaretler

Bir function ifadesinin, arrow function'ın veya metodun başına async eklediğinizde iki şey olur:

  1. Fonksiyon her zaman bir promise döndürür. return ile ne verirseniz, promise'in resolve olan değeri o olur.
  2. Artık fonksiyonun içinde await kullanabilirsiniz.
index.js
Output
Click Run to see the output here.

Gördüğün gibi result artık string değil — string'e çözümlenecek bir promise. greet fonksiyonunun içinde await ya da asenkron bir işlem olmamasına rağmen, async anahtar kelimesi dönüş değerini otomatik olarak bir promise'e sarmalıyor. Fonksiyon hata fırlatırsa promise reject olur.

await ile Promise Çözümlenene Kadar Bekleme

Bir async fonksiyonun içinde await somePromise yazdığında, promise çözümlenene kadar o fonksiyon duraklar ve ardından çözümlenen değeri sana verir. Promise reject olursa await hata fırlatır.

index.js
Output
Click Run to see the output here.

Çıktı sırasına dikkat edin. "geri sayım başladı", "2"'den önce basılır — çünkü await yalnızca async fonksiyonunu duraklatır, programın geri kalanını değil. Event loop çalışmaya devam eder; countdown ise her wait promise'i çözüldüğünde kaldığı yerden devam eder.

Promise benzeri her şeyi await edebilirsiniz. await 42 bile geçerlidir — promise olmayan değerler otomatik olarak Promise.resolve(42) ile sarmalanıp anında çözülür.

try/catch ile Hata Yönetimi

Düz promise'lerde hataları .catch() zinciriyle yakalarsınız. async/await ile ise reddedilen bir promise, normal yollarla yakalayabileceğiniz bir exception'a dönüşür:

index.js
Output
Click Run to see the output here.

Tek bir try/catch, içindeki tüm await ifadelerini kapsar. Ağ hataları, JSON parse hataları ve kendi yazdığın throw'ların hepsi aynı catch bloğunda yakalanır. İç içe geçmiş .then/.catch zincirlerine kıyasla ciddi bir rahatlama.

Dikkat edilmesi gereken bir nokta var: fetch yalnızca ağ hatalarında reject olur, HTTP 4xx/5xx durumlarında değil. Bu yüzden res.ok'u kendin kontrol edip hata fırlatman gerekir — gerçek projelerde sürekli karşılaşacağın bir kalıp.

Döngü İçinde Gereksiz Yere await Kullanma

Async/await'in en sık düşülen tuzağı budur. Döngü içinde sıralı await kullanmak, her iterasyonun bir öncekini beklemesi anlamına gelir:

index.js
Output
Click Run to see the output here.

sequential yaklaşık 900ms sürer, parallel ise yaklaşık 300ms. Kural basit: işler birbirinin sonucuna bağlı değilse, hepsini aynı anda başlatıp Promise.all ile bekleyin. Yalnızca bir sonraki çağrının gerçekten önceki sonuca ihtiyacı varsa tek tek await kullanın.

Dizi ve listelerde ise standart kalıp Promise.all(items.map(async (x) => ...)) şeklindedir. İçinde await olan düz bir for...of döngüsü sıralı çalışır — bazen tam olarak bunu istersiniz (rate limit, sıralama gibi durumlarda), ama çoğu zaman istemezsiniz.

async/await ve Promise'leri birlikte kullanmak

İkisinden birini seçmek zorunda değilsiniz. async fonksiyonlar zaten promise döndürür ve await herhangi bir promise ile çalışır — yani ikisini rahatça harmanlayabilirsiniz:

index.js
Output
Click Run to see the output here.

İki stil de birbirinin yerine kullanılabilir. Kod yukarıdan aşağıya daha akıcı okunuyorsa await tercih edin; hızlı tek seferlik bir iş yapıyorsanız veya async bağlamının dışındaysanız .then daha pratik olur.

Top-level await (ES Modüllerinde)

Eskiden await'i mutlaka bir async fonksiyonun içine sarmak zorundaydınız; çünkü script'in en üst seviyesinde await kullanmaya izin verilmiyordu. Ama bu iş değişti: artık bir ES modülü içinde (yani .mjs uzantılı bir dosyada ya da <script type="module"> içinde) doğrudan en üst seviyede await yazabilirsiniz:

// bir ES modülünde
const res = await fetch("https://jsonplaceholder.typicode.com/users/1");
const user = await res.json();
console.log(user.name);

Top-level await, awaited edilen promise tamamlanana kadar modülün yüklenmesini geciktirir — o modülü import eden herkes de bu bekleyişe dahil olur. Konfigürasyon yükleme ve dinamik import'lar için oldukça kullanışlıdır, ama idareli kullanın: yavaş bir top-level await, modülü import eden herkesi bloklar.

CommonJS dosyalarında veya klasik inline script'lerde bu hâlâ SyntaxError verir. Bilinen çözüm, hemen çağrılan bir async fonksiyon kullanmaktır:

index.js
Output
Click Run to see the output here.

Sık Düşülen Tuzaklar

Kafa karıştıran klasik hatalara kısa bir bakış:

  • async yazmayı unutmak. Normal bir fonksiyonun içinde await kullanmak syntax hatası verir. Çözüm ya fonksiyonun başına async eklemek ya da async yardımcı fonksiyonu .then ile çağırmak.
  • Sonucu await etmeyi unutmak. const data = getJSON(url); dediğinde elinde verinin kendisi değil, bir promise olur. Bunu doğrudan değer gibi kullanırsan çıktıda [object Promise] ifadesini görürsün.
  • Yakalanmayan hatalar (unhandled rejections). Bir async fonksiyonu çağırıp bırakırsan (doWork();), .catch eklemediğin ya da try/catch bloğu içinde await etmediğin sürece hatalar sessizce yutulur.
  • forEach ile async callback kullanmak. array.forEach(async (x) => await something(x)) aslında hiçbir şeyi beklemez; çünkü forEach geri dönen promise'ları görmezden gelir. Bunun yerine for...of ile await kullan ya da Promise.all(array.map(...)) tercih et.
index.js
Output
Click Run to see the output here.

Çalıştırdığınızda "tamamlandı?" ifadesi "bitti" mesajlarından önce yazdırılır; çünkü broken hiçbir şeyi beklemeden geri döner. fixed ise her şeyi bekler ve "tamamlandı!" en sonda yazdırılır.

async/await ne zaman kullanılmalı?

Birden fazla asenkron adımı sırayla çalıştıran ya da try/catch tarzı hata yönetimine ihtiyaç duyan her kodda varsayılan tercihiniz async/await olsun. Tek satırlık basit işlerde, kendisi bir şey beklemeden promise döndüren kütüphane kodlarında veya Promise.race, zincirleme .finally() gibi kombinatörlere gerçekten ihtiyacınız olduğunda düz promise'lerle devam edin.

Yerinde kullanıldığında async/await, asenkron kodu bir yemek tarifi gibi okunur kılar: önce şunu yap, sonra şunu, sonra şunu. Event loop arka planda işini yapmaya devam eder — siz sadece callback'lerle düşünmeyi bırakırsınız.

Sırada: fetch API

Buradaki örneklerin çoğunda fetch'i "herhangi bir asenkron iş" için bir yer tutucu olarak kullandık. Artık ona hak ettiği şekilde bakma zamanı: istek ve yanıtların nasıl çalıştığı, JSON işleme, header ayarlama ve fetch'in HTTP hatalarında neden reject etmediği. Bunların hepsi bir sonraki sayfada.

Sıkça Sorulan Sorular

JavaScript'te async/await ne işe yarar?

async/await, promise'lerle çalışmayı kolaylaştıran bir sözdizimidir; asenkron kodu neredeyse senkron kod gibi yazmanıza olanak tanır. async bir fonksiyonu promise döndüren bir fonksiyon olarak işaretler; await ise o fonksiyon içinde beklenen promise çözümlenene kadar yürütmeyi duraklatır ve size sonucu (resolved value) verir. Aslında arka planda yine promise'ler çalışıyor — sadece okuması çok daha rahat.

await'i async fonksiyon dışında kullanabilir miyim?

Bir ES modülünün en üst seviyesinde evet, buna top-level await deniyor. Ama normal fonksiyonların içinde veya CommonJS dosyalarında olmaz: async olmayan bir fonksiyonda await kullanmak sözdizimi hatası verir. Çözüm genelde kodu bir async fonksiyona sarıp çağırmak ya da dosyayı ES modülüne çevirmektir.

async/await ile hataları nasıl yakalarım?

await ettiğiniz çağrıları try/catch bloğuyla sarmalayın. await edilen bir promise reddedildiğinde (reject olduğunda) bu, catch bloğunun yakalayabileceği bir exception'a dönüşür. await etmediğiniz, arka planda çalışan işler için ise dönen promise'e bir .catch() eklemeyi unutmayın; aksi halde unhandled rejection uyarısı alırsınız.

await tüm programı durdurur mu?

Hayır. await yalnızca içinde bulunduğu async fonksiyonu duraklatır. Event loop çalışmaya devam eder — timer'lar tetiklenir, diğer asenkron işler ilerler, arayüz donmaz. Çağıran kod ise anında pending (beklemede) bir promise alır ve kaldığı yerden devam eder.

Coddy ile kodlamayı öğren

BAŞLA