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.
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ı: bozulduMDN 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 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
thenmetodu 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
awaitifadesi 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.
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 bitirtry/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.
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
// nullTek 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.
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.
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()])](/assets/blog/javascript-async-await-b5.webp)
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.
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 | İşlemler | Hata 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şlar | pb, 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şlar | Hiç 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.
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ı 6Dizi 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.
// ayar.mjs
const ayar = await Promise.resolve({ port: 3000 });
console.log(ayar.port); // 3000CommonJS 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:
// ornek.cjs
function f() {
const x = await Promise.resolve(1);
}
// SyntaxError: await is only valid in async functions and the top level bodies of modulesModü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

- 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
awaitek bir microtask demektir; değeri açmanız gerekmiyorsa eklemeyin. - Bağımsız işlemleri sırayla beklemek: toplam süre gereksiz uzar;
Promise.allkullanı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...ofveyamap+Promise.allkullanın. - Ana fonksiyonda catch olmaması: en dıştaki async çağrının sonucuna bir
catchekleyin; 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