async/await, Promise döndüren kodu senkron kod gibi okunur biçimde yazmanızı sağlayan JavaScript sözdizimidir. async ile tanımlanan bir fonksiyon her zaman bir Promise döndürür; await ise bir Promise sonuçlanana kadar yalnızca o fonksiyonun çalışmasını duraklatır ve fulfilled değerini verir. Promise reddedilirse await hatayı fırlatır, siz de onu sıradan bir try/catch ile yakalarsınız.

Bu yazıda async fonksiyonların dönüş değerini, await ile hata yönetimini, işlemleri sıralı mı paralel mi bekleyeceğinizi ve modüllerde üst düzey (top-level) await kullanımını Node.js 24 ile denenmiş örneklerle anlatıyoruz. Temel kavramlar için önce Promise nedir yazısını okumanızı öneririz.

async fonksiyon her zaman Promise döndürür

Bir async fonksiyon normal bir değer döndürse bile çağıran taraf bir Promise alır; değer bu Promise'in fulfilled değeri olur. Fonksiyon içinde yakalanmayan bir hata fırlatılırsa dönen Promise o hatayla reddedilir.

javascript
async function sayi() {
  return 1;
}
const s = sayi();
console.log(s instanceof Promise); // true
s.then((v) => console.log(v)); // 1

async function hatali() {
  throw new Error('bozuldu');
}
hatali().catch((e) => console.log('yakalandı:', e.message)); // yakalandı: bozuldu

MDN küçük bir ayrıntıya dikkat çeker: async fonksiyonun dönüşü Promise.resolve ile sarılmış gibi davranır ama aynı şey değildir. Bir Promise döndürdüğünüzde async fonksiyon size her zaman farklı bir Promise referansı verir; bu yüzden döndürülen değeri === ile orijinal Promise'e karşılaştırmak false sonuç verir.

await ne yapar?

await ne yapar? Değer bir Promise ise sonuçlanması beklenir ve fulfilled değeri döner., Değer then metodu olan bir nesne (thenable) ise o metot çağrılarak çözülür., Değer Promise...

await ifade yazdığınızda ifade önce Promise.resolve ile aynı yöntemle bir Promise'e çevrilir, sonra beklenir:

  • Değer bir Promise ise sonuçlanması beklenir ve fulfilled değeri döner.
  • Değer then metodu olan bir nesne (thenable) ise o metot çağrılarak çözülür.
  • Değer Promise değilse (örneğin await 20) zaten çözülmüş bir Promise gibi davranılır ve değerin kendisi döner.
  • Promise reddedilirse await ifadesi ret sebebini fırlatır.

Önemli nokta: await programın tamamını durdurmaz, yalnızca içinde bulunduğu async fonksiyonun o noktadan sonraki kısmını erteler. Kontrol çağırana döner ve diğer kodlar çalışmaya devam eder. Bekleme bittiğinde fonksiyonun devamı bir microtask olarak sıraya girer. Bu sıralamanın ayrıntısını event loop yazımızda bulabilirsiniz.

javascript
async function foo(ad) {
  console.log(ad, 'başla');
  await null;
  console.log(ad, 'bitir');
}
foo('Birinci');
foo('İkinci');
console.log('senkron kod bitti');
// Birinci başla
// İkinci başla
// senkron kod bitti
// Birinci bitir
// İkinci bitir

try/catch/finally ile hata yönetimi

await reddedilen Promise'i bir istisnaya çevirdiği için asenkron hataları senkron kodla aynı try/catch/finally yapısıyla ele alabilirsiniz. finally bloğu başarıda da hatada da çalışır; bağlantı kapatma, yükleniyor bayrağını sıfırlama gibi temizlik işleri için uygundur.

javascript
const bekle = (ms, deger) => new Promise((r) => setTimeout(() => r(deger), ms));
const hataVer = (ms, mesaj) => new Promise((_, rej) => setTimeout(() => rej(new Error(mesaj)), ms));

async function kullaniciGetir(id) {
  try {
    if (id < 0) await hataVer(50, 'geçersiz id');
    const kullanici = await bekle(50, { id, ad: 'Ayşe' });
    return kullanici.ad;
  } catch (e) {
    console.log('hata:', e.message);
    return null;
  } finally {
    console.log('istek bitti');
  }
}

(async () => {
  console.log(await kullaniciGetir(1));
  console.log(await kullaniciGetir(-1));
})();
// istek bitti
// Ayşe
// hata: geçersiz id
// istek bitti
// null

Tek bir çağrının hatasını yerinde ele almak istiyorsanız try bloğu yerine await etmeden önce .catch() zincirleyebilirsiniz. MDN, bunun yalnızca fonksiyon hatayı senkron fırlatmayıp reddedilmiş Promise döndürüyorsa işe yaradığını hatırlatır.

javascript
async function f() {
  return Promise.reject(new Error('yedek kullan'));
}
(async () => {
  const yanit = await f().catch((e) => {
    console.log(e.message); // yedek kullan
    return 'varsayılan';
  });
  console.log(yanit); // varsayılan
})();

Error türleri, cause ve özel hata sınıfları için hata yönetimi rehberimize bakın.

Promise zincirini async/await'e çevirmek

MDN'e göre async/await, Promise zincirleriyle aynı eşzamanlılık davranışına sahiptir; yalnızca yazım biçimi değişir. Bu yüzden mevcut bir then zincirini adım adım dönüştürebilirsiniz: her then işleyicisi bir await satırına, sondaki catch ise bir try/catch bloğuna karşılık gelir. Çağrılan fonksiyonlar değişmez, çünkü ikisi de aynı Promise'leri kullanır.

javascript
const bekle = (ms, deger) => new Promise((r) => setTimeout(() => r(deger), ms));
const kullaniciBul = (id) => bekle(30, { id, ad: 'Deniz' });
const siparisleriGetir = (k) => bekle(30, [`${k.ad}-1`, `${k.ad}-2`]);

function zincirle(id) {
  return kullaniciBul(id)
    .then((k) => siparisleriGetir(k))
    .then((s) => s.length)
    .catch(() => 0);
}

async function beklemeli(id) {
  try {
    const k = await kullaniciBul(id);
    const s = await siparisleriGetir(k);
    return s.length;
  } catch {
    return 0;
  }
}

zincirle(7).then((n) => console.log('zincir:', n)); // zincir: 2
beklemeli(7).then((n) => console.log('await:', n)); // await: 2

İkinci sürümde ara değerler adlandırılmış değişkenlerde durduğu için okunması ve hata ayıklaması genellikle daha kolaydır. Bir adımın sonucunu birkaç adım sonra yeniden kullanmanız gerektiğinde bu fark daha belirgin hale gelir; zincirde aynı değeri taşımak için iç içe then yazmak gerekirken, await ile değişken kapsamda kalır.

Sıralı ve paralel await

Sıralı ve paralel await: await a(); await b();, const pa = a(); const pb = b(); await pa; await pb;, await Promise.all([a(), b()]), await Promise.allSettled([a(), b()])

En sık performans hatası, birbirine bağlı olmayan işlemleri tek tek beklemektir. Her await bir öncekinin bitmesini beklediği için iki adet 300 ms'lik işlem toplam yaklaşık 600 ms sürer. İşlemleri önce başlatıp Promise.all ile birlikte beklerseniz süre en yavaş işlem kadar olur.

javascript
const bekle = (ms, deger) => new Promise((r) => setTimeout(() => r(deger), ms));

async function sirali() {
  const t = Date.now();
  const a = await bekle(300, 'a');
  const b = await bekle(300, 'b');
  console.log(a, b, Math.round((Date.now() - t) / 100) * 100, 'ms civarı');
}

async function paralel() {
  const t = Date.now();
  const [a, b] = await Promise.all([bekle(300, 'a'), bekle(300, 'b')]);
  console.log(a, b, Math.round((Date.now() - t) / 100) * 100, 'ms civarı');
}

(async () => {
  await sirali();  // a b 600 ms civarı
  await paralel(); // a b 300 ms civarı
})();
YazımİşlemlerHata davranışı
await a(); await b();Sırayla, b ancak a bitince başlarİlk hata fonksiyonu durdurur
const pa = a(); const pb = b(); await pa; await pb;Aynı anda başlarpb, pa beklenirken reddedilirse yakalanmamış ret uyarısı oluşabilir
await Promise.all([a(), b()])Aynı anda başlarİlk ret anında yakalanır
await Promise.allSettled([a(), b()])Aynı anda başlarHiç reddetmez, her sonucu raporlar

Hangi birleştiriciyi seçeceğinizi Promise.all, allSettled, race ve any karşılaştırmasında ayrıntılı ele aldık.

Döngülerde await: forEach tuzağı

Array.prototype.forEach callback'in döndürdüğü Promise'i beklemez. İçine async fonksiyon verirseniz döngü hemen biter ve sonuçlar sonra gelir. Sıralı işlem için for...of içinde await, paralel işlem için map ile Promise dizisi üretip Promise.all kullanın.

javascript
const sayilar = [1, 2, 3];
async function ikiKati(x) { return x * 2; }

sayilar.forEach(async (x) => {
  const y = await ikiKati(x);
  console.log('forEach içinde', y);
});
console.log('forEach bitti');

(async () => {
  const sonuc = await Promise.all(sayilar.map((x) => ikiKati(x)));
  console.log(sonuc); // [ 2, 4, 6 ]
  for (const x of sayilar) {
    console.log('sıralı', await ikiKati(x));
  }
})();
// forEach bitti
// forEach içinde 2
// forEach içinde 4
// forEach içinde 6
// [ 2, 4, 6 ]
// sıralı 2
// sıralı 4
// sıralı 6

Dizi yöntemlerinin genel davranışı için map, filter ve reduce yazımıza bakabilirsiniz.

Modüllerde top-level await

await normalde yalnızca async fonksiyon içinde geçerlidir. Bunun tek istisnası ES modülleridir: bir modülün en üst seviyesinde await doğrudan kullanılabilir. Bu modülü içe aktaran modüller, onun çalışması bitene kadar bekler; diğer alt modüllerin yüklenmesi ise engellenmez. Node.js'te dosyayı .mjs uzantısıyla ya da package.json içinde "type": "module" ile modül olarak çalıştırabilirsiniz.

javascript
// ayar.mjs
const ayar = await Promise.resolve({ port: 3000 });
console.log(ayar.port); // 3000

CommonJS betiğinde veya normal bir fonksiyon içinde await kullanırsanız sözdizimi hatası alırsınız. Node.js 24'teki mesaj şöyledir:

javascript
// ornek.cjs
function f() {
  const x = await Promise.resolve(1);
}
// SyntaxError: await is only valid in async functions and the top level bodies of modules

Modül sistemi hakkında daha fazlası için ES modülleri import/export yazımıza göz atın.

return await mı, return mü?

Async fonksiyon sonunda bir Promise'i await etmeden döndürmek de çalışır. Ancak MDN iki farkı belirtir: return await kullandığınızda Promise reddedilirse hata o fonksiyon içinde fırlatılır, dolayısıyla çevreleyen try/catch onu yakalar ve fonksiyon yığın izinde (stack trace) görünür. MDN'e göre return await promise en az return promise kadar hızlıdır, bu yüzden üslup dışında genellikle tercih edilir.

Sık yapılan hatalar

Sık yapılan hatalar: await'i unutmak, Gereksiz await, Bağımsız işlemleri sırayla beklemek, Promise.all'a fonksiyon vermek
  • await'i unutmak: değer yerine bekleyen bir Promise ile çalışırsınız. MDN, bunun ancak tip uyuşmazlığı ortaya çıktığında fark edildiğini belirtir.
  • Gereksiz await: MDN'e göre her await ek bir microtask demektir; değeri açmanız gerekmiyorsa eklemeyin.
  • Bağımsız işlemleri sırayla beklemek: toplam süre gereksiz uzar; Promise.all kullanın.
  • Promise.all'a fonksiyon vermek: Promise.all([f, g]) fonksiyonları çağırmaz; Promise.all([f(), g()]) yazmalısınız.
  • forEach içinde await: döngü beklemez; for...of veya map + Promise.all kullanın.
  • Ana fonksiyonda catch olmaması: en dıştaki async çağrının sonucuna bir catch ekleyin; aksi halde yakalanmamış ret uyarısı alırsınız.

Sık Sorulan Sorular

await ana iş parçacığını (thread) bloklar mı?

Hayır. MDN'e göre await yalnızca sonucuna bağlı olan kodu erteler; kontrol çağırana döner ve olay döngüsü diğer işleri yürütmeye devam eder.

async fonksiyon içinde hiç await yoksa ne olur?

Gövde baştan sona senkron çalışır, ama fonksiyon yine de bir Promise döndürür. İlk await'e kadar olan kısım her zaman senkron yürütülür.

Top-level await her dosyada çalışır mı?

Hayır, yalnızca ES modüllerinde. Klasik betiklerde ve CommonJS dosyalarında async bir fonksiyon (örneğin hemen çağrılan bir async arrow function) içine almanız gerekir.

async/await, Promise'in yerini alır mı?

Hayır, onun üzerine kuruludur. await bir Promise'i bekler; eşzamanlı işler için Promise.all gibi yöntemler hâlâ gereklidir.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: JavaScript öğrenme rehberi · Promise nedir · Promise.all, allSettled, race, any · Hata yönetimi · ES modülleri · Node.js REST API geliştirme