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:
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:
- Fonksiyon her zaman bir promise döndürür.
returnile ne verirseniz, promise'in resolve olan değeri o olur. - Artık fonksiyonun içinde
awaitkullanabilirsiniz.
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.
Çı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:
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:
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:
İ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:
Sık Düşülen Tuzaklar
Kafa karıştıran klasik hatalara kısa bir bakış:
asyncyazmayı unutmak. Normal bir fonksiyonun içindeawaitkullanmak syntax hatası verir. Çözüm ya fonksiyonun başınaasynceklemek ya da async yardımcı fonksiyonu.thenile çağırmak.- Sonucu
awaitetmeyi 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();),.catcheklemediğin ya datry/catchbloğu içindeawaitetmediğin sürece hatalar sessizce yutulur. forEachile async callback kullanmak.array.forEach(async (x) => await something(x))aslında hiçbir şeyi beklemez; çünküforEachgeri dönen promise'ları görmezden gelir. Bunun yerinefor...ofileawaitkullan ya daPromise.all(array.map(...))tercih et.
Ç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.