Async/Await
Neden Bu Konu Önemli?
Promise'ler callback hell'i çözdü, ama uzun chain'ler hâlâ okunması zor olabiliyor. then().then().then() yapısı, senkron kod yazmaya alışmış bir geliştiricinin gözüne doğal gelmiyor. Peki ya asenkron kodu, senkron kod gibi yazabilsek?
İşte async/await tam olarak bunu sağlar. ES2017'de dile eklenen bu syntax, Promise'lerin üzerine inşa edilmiş bir sözdizimsel şeker (syntactic sugar) dir. Altta yine Promise'ler çalışır ama kodun görüntüsü senkron koda dönüşür.
Düşün ki bir tercüman var. Yabancı bir dili anlıyorsun ama konuşurken zorlanıyorsun. Tercüman senin ana dilinde söylediğini yabancı dile çeviriyor. Async/await da senin "senkron düşünceni" JavaScript motoruna "Promise dilinde" çeviren bir tercümandır.
Async Fonksiyonlar
Bir fonksiyonun başına async keyword'ü koyduğunda, o fonksiyon her zaman bir Promise döner. İçinden normal bir değer dönsen bile, otomatik olarak Promise.resolve() ile sarılır.
// async fonksiyon tanımlama
async function selamla() {
return "Merhaba!";
}
// Yukarıdaki aslında şuna eşdeğer:
function selamla() {
return Promise.resolve("Merhaba!");
}
// Her ikisi de aynı şekilde kullanılır:
selamla().then((mesaj) => console.log(mesaj)); // "Merhaba!"Farklı fonksiyon türlerinin hepsi async olabilir:
// Function declaration
async function fonksiyon1() {
return "declaration";
}
// Function expression
const fonksiyon2 = async function () {
return "expression";
};
// Arrow function
const fonksiyon3 = async () => {
return "arrow";
};
// Method (nesne içinde)
const nesne = {
async metot() {
return "method";
},
};
// Class method
class Servis {
async veriGetir() {
return "class method";
}
}Async Fonksiyon Ne Zaman Reject Olur?
Async fonksiyon içinde bir hata fırlatılırsa (throw), döndürdüğü Promise otomatik olarak reject olur:
async function hataFirlat() {
throw new Error("Bir şeyler ters gitti!");
// Bu satıra asla ulaşılmaz
}
// Promise.reject(new Error("...")) ile aynı
hataFirlat()
.catch((err) => console.error(err.message));
// "Bir şeyler ters gitti!"Await: Promise'i Bekle
await keyword'ü sadece async fonksiyonların içinde kullanılabilir (bir istisna var: top-level await — ilerleyen bölümlerde). Bir Promise'in sonucunu bekler ve fulfilled değerini döner.
// await olmadan — Promise chain
function kullaniciyiGoster() {
return fetch("/api/user/1")
.then((response) => response.json())
.then((user) => {
console.log(user.name);
});
}
// await ile — senkron görünümlü asenkron kod
async function kullaniciyiGoster() {
const response = await fetch("/api/user/1"); // Promise'i bekle
const user = await response.json(); // Bunu da bekle
console.log(user.name); // Artık veri burada
}İkisi de aynı şeyi yapar. Ama await versiyonu çok daha okunabilir — sanki senkron kod yazıyormuşsun gibi.
Await Nasıl Çalışır?
await bir Promise'le karşılaştığında:
Fonksiyonun çalışmasını askıya alır (pause)
Promise tamamlanana kadar bekler
Promise fulfilled olursa, resolved değerini döner
Promise rejected olursa, hata fırlatır (throw gibi davranır)
Fonksiyonun geri kalanı microtask olarak devam eder
async function ornek() {
console.log("1 - await öncesi (senkron)");
const sonuc = await Promise.resolve("veri");
// ↑ Buradan sonrası askıya alınır, microtask olarak devam eder
console.log("2 - await sonrası:", sonuc);
}
console.log("A");
ornek();
console.log("B");
// Çıktı:
// A
// 1 - await öncesi (senkron)
// B ← ornek() askıda, senkron kod devam ediyor
// 2 - await sonrası: veri ← microtask olarak devam etti💡 İpucu:
awaitfonksiyonu bloklamaz — sadece o fonksiyonun çalışmasını askıya alır. Dışarıdaki kod çalışmaya devam eder. Tıpkı bir telefonla konuşurken "beklemede kal" demek gibi — sen başka işlerinle ilgilenirsin, karşı taraf hazır olunca devam edersin.
Error Handling: try/catch ile
Promise chain'deki .catch() yerine, async/await'te tanıdık try/catch bloğu kullanılır:
// Promise chain'de hata yönetimi
function veriCek() {
return fetch("/api/data")
.then((response) => {
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
})
.catch((err) => {
console.error("Hata:", err.message);
});
}
// async/await ile hata yönetimi — çok daha doğal
async function veriCek() {
try {
const response = await fetch("/api/data");
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
console.log("Veri:", data);
return data;
} catch (err) {
console.error("Hata:", err.message);
} finally {
console.log("İşlem tamamlandı (başarılı veya değil)");
}
}Spesifik Hata Yakalama
// Farklı hata türlerini ayrı ayrı yönetme
async function kullaniciIslemleri(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (response.status === 404) {
throw new NotFoundError(`Kullanıcı #${userId} bulunamadı`);
}
if (response.status === 403) {
throw new ForbiddenError("Bu kullanıcıyı görme yetkiniz yok");
}
if (!response.ok) {
throw new Error(`Beklenmeyen hata: HTTP ${response.status}`);
}
return await response.json();
} catch (err) {
if (err instanceof NotFoundError) {
console.log("Kullanıcı yok, misafir sayfası göster");
return null;
}
if (err instanceof ForbiddenError) {
console.log("Yetki yok, giriş sayfasına yönlendir");
window.location.href = "/login";
return null;
}
// Beklenmeyen hatalar — yukarı fırlat
throw err;
}
}
// Custom Error sınıfları
class NotFoundError extends Error {
constructor(message) {
super(message);
this.name = "NotFoundError";
}
}
class ForbiddenError extends Error {
constructor(message) {
super(message);
this.name = "ForbiddenError";
}
}Paralel Çalıştırma
Async/await'in en büyük tuzağı, her şeyi gereksiz yere sıralı yapmaktır. Birbirinden bağımsız asenkron işlemleri sıralı yapmak büyük performans kaybına neden olur.
Yanlış: Gereksiz Sıralı Await
// ❌ YANLIŞ — iki bağımsız istek sırayla yapılıyor
async function dashboard() {
const kullanici = await fetch("/api/user").then((r) => r.json());
// ↑ Bu bitmeden alttaki BAŞLAMAZ
const siparisler = await fetch("/api/orders").then((r) => r.json());
// ↑ Bu bitmeden alttaki BAŞLAMAZ
const bildirimler = await fetch("/api/notifications").then((r) => r.json());
return { kullanici, siparisler, bildirimler };
}
// Toplam süre: ~600ms (200ms + 200ms + 200ms) — gereksiz yavaş!Doğru: Promise.all ile Paralel
// ✅ DOĞRU — bağımsız istekler paralel
async function dashboard() {
const [kullanici, siparisler, bildirimler] = await Promise.all([
fetch("/api/user").then((r) => r.json()),
fetch("/api/orders").then((r) => r.json()),
fetch("/api/notifications").then((r) => r.json()),
]);
return { kullanici, siparisler, bildirimler };
}
// Toplam süre: ~200ms (en yavaş olanın süresi) — 3 kat hızlı!Karışık Senaryo: Bazıları Bağımlı, Bazıları Bağımsız
// Gerçek dünya: Önce kullanıcı gel, sonra onun verilerini paralel çek
async function dashboardYukle(userId) {
// 1. Adım: Kullanıcı bilgisi (diğerleri buna bağımlı)
const kullanici = await getUser(userId);
// 2. Adım: Kullanıcıya bağımlı veriler PARALEL çekilir
const [siparisler, favoriler, adresler] = await Promise.all([
getOrders(kullanici.id),
getFavorites(kullanici.id),
getAddresses(kullanici.id),
]);
return { kullanici, siparisler, favoriler, adresler };
}
// Adım 1: ~200ms (sıralı — zorunlu)
// Adım 2: ~200ms (paralel — 3 istek aynı anda)
// Toplam: ~400ms (sıralı olsa ~800ms olurdu)Başlat-Sonra-Bekle Pattern'ı
Promise.all kullanmadan da paralel çalıştırabilirsin. Promise'leri başlatıp, sonra await edersin:
// Promise'leri BAŞLAT (await etme — henüz bekleme)
async function parallelPattern() {
// Her iki istek AYNI ANDA başlar
const kullaniciPromise = fetch("/api/user").then((r) => r.json());
const siparislerPromise = fetch("/api/orders").then((r) => r.json());
// Sonuçları BEKLEyerek al
const kullanici = await kullaniciPromise;
const siparisler = await siparislerPromise;
return { kullanici, siparisler };
}⚠️ Dikkat: Bu pattern'da dikkat: eğer
kullaniciPromisereject olursa,siparislerPromise'in hatası unhandled kalabilir (zaten resolve olduysa sorun yok ama reject olursa sorun).Promise.allbu sorunu yönetir — tercih edin.
Async/Await ile Döngüler
Döngülerde async/await kullanımı önemli nüanslar içerir.
for...of ile Sıralı Çalıştırma
// Sıralı: Her istek öncekini bekler
async function siralaCalistir(urller) {
const sonuclar = [];
for (const url of urller) {
const response = await fetch(url);
const data = await response.json();
sonuclar.push(data);
console.log(`✅ ${url} tamamlandı`);
}
return sonuclar;
}
// Kullanım
await siralaCalistir(["/api/a", "/api/b", "/api/c"]);
// ✅ /api/a tamamlandı
// ✅ /api/b tamamlandı (a bittikten sonra)
// ✅ /api/c tamamlandı (b bittikten sonra)forEach ile ÇALIŞMAZ!
// ❌ YANLIŞ: forEach async/await ile düzgün çalışmaz!
async function hataliOrnek(urller) {
urller.forEach(async (url) => {
const response = await fetch(url);
const data = await response.json();
console.log(data);
// await burada forEach'i BEKLETMEZ
// forEach, async callback'leri "ateşle ve unut" yapar
});
console.log("Bitti!");
// "Bitti!" tüm fetch'lerden ÖNCE yazılır!
}
// ✅ DOĞRUSU: for...of veya Promise.all kullan
async function dogruOrnek(urller) {
for (const url of urller) {
const response = await fetch(url);
const data = await response.json();
console.log(data);
}
console.log("Bitti!"); // Gerçekten hepsi bittikten sonra
}⚠️ Dikkat:
Array.forEach,Array.map,Array.filtergibi dizi metotlarındaasynccallback kullanmak, beklediğin gibi çalışmaz.forEachcallback'in döndürdüğü Promise'i hiç beklemez, göz ardı eder.
map ile Paralel + Promise.all
// Paralel: Tüm istekler aynı anda başlar
async function paralelCalistir(urller) {
const sonuclar = await Promise.all(
urller.map(async (url) => {
const response = await fetch(url);
return response.json();
})
);
return sonuclar;
}
// map ile async callback → Promise dizisi döner
// Promise.all ile hepsini beklerizKontrollü Eşzamanlılık (Concurrency Limiting)
Bazen 100 istek yapmak istiyorsun ama hepsini aynı anda başlatmak sunucuyu ezer. Kaçar kaçar çalıştıracağını kontrol edebilirsin:
// Aynı anda en fazla N istek çalıştırma
async function kontrolluParalel(islemler, esZamanlılik = 3) {
const sonuclar = [];
for (let i = 0; i < islemler.length; i += esZamanlılik) {
const parca = islemler.slice(i, i + esZamanlılik);
const parcaSonuclari = await Promise.all(
parca.map((islem) => islem())
);
sonuclar.push(...parcaSonuclari);
console.log(`${i + parca.length}/${islemler.length} tamamlandı`);
}
return sonuclar;
}
// Kullanım: 10 istek, 3'er 3'er
const istekler = Array.from({ length: 10 }, (_, i) => {
return () => fetch(`/api/items/${i}`).then((r) => r.json());
});
const tumu = await kontrolluParalel(istekler, 3);
// 3/10 tamamlandı
// 6/10 tamamlandı
// 9/10 tamamlandı
// 10/10 tamamlandıTop-Level Await
ES2022 ile birlikte, async fonksiyon dışında da — modül seviyesinde — await kullanmak mümkün hale geldi. Buna top-level await denir.
// ❌ Eskiden: Modül yüklenirken async işlem yapmak zordu
// config.js (ESKİ YÖNTEM)
let config;
async function init() {
const response = await fetch("/api/config");
config = await response.json();
}
init(); // Ama bu bitmeden config kullanılamaz!
export { config }; // undefined olabilir!
// ✅ Şimdi: Top-level await ile (ES Module'de)
// config.js (YENİ YÖNTEM)
const response = await fetch("/api/config");
const config = await response.json();
export { config }; // Garanti dolu — modül yüklenene kadar beklerTop-Level Await Kuralları
// ✅ ÇALIŞIR: ES Module'de (.mjs veya type: "module")
// module.mjs
const data = await fetch("/api/data").then((r) => r.json());
export default data;
// ✅ ÇALIŞIR: <script type="module"> içinde
// <script type="module">
// const result = await fetch("/api/data");
// </script>
// ❌ ÇALIŞMAZ: CommonJS'te (require ile)
// Bu bir CJS dosyası
// const data = await fetch("/api/data"); // SyntaxError!
// ❌ ÇALIŞMAZ: Normal <script> tag'inde
// <script>
// const result = await fetch("/api/data"); // SyntaxError!
// </script>Top-Level Await'in Modül Yüklemeye Etkisi
// a.mjs
console.log("A: başladı");
await new Promise((resolve) => setTimeout(resolve, 2000));
console.log("A: bitti");
export const a = "A verisi";
// b.mjs
import { a } from "./a.mjs";
// ↑ Bu satır, a.mjs'deki await tamamlanana kadar BEKLER
console.log("B: a'yı kullanıyor:", a);💡 İpucu: Top-level await, o modülü import eden tüm modülleri bekletir. Bu güçlü bir özellik ama dikkatli kullanılmalı — gereksiz yere uzun süren top-level await'ler, uygulamanın başlama süresini artırır.
Async/Await Best Practices
1. Her Zaman Hataları Yakala
// ❌ Hata yakalanmamış
async function tehlikeli() {
const data = await fetch("/api/data").then((r) => r.json());
return data;
}
// Eğer fetch hata verirse, unhandled rejection!
// ✅ try/catch ile
async function guvenli() {
try {
const data = await fetch("/api/data").then((r) => r.json());
return data;
} catch (err) {
console.error("Hata:", err.message);
return null; // Varsayılan değer
}
}
// ✅ Veya çağıran tarafta yakala
guvenli()
.then((data) => console.log(data))
.catch((err) => console.error(err));2. Gereksiz await'ten Kaçın
// ❌ Gereksiz await — zaten Promise dönüyor
async function getUser(id) {
const response = await fetch(`/api/users/${id}`);
const data = await response.json();
return data; // Gereksiz: async zaten Promise döner
}
// ✅ Son ifadede await gerekmez (ama hata yakalama istiyorsan gerekir)
async function getUser(id) {
const response = await fetch(`/api/users/${id}`);
return response.json(); // Direkt Promise dön — bir await azaldı
}
// ⚠️ AMA: try/catch kullanıyorsan son await GEREKLI
async function getUser(id) {
try {
const response = await fetch(`/api/users/${id}`);
return await response.json(); // await LAZIM — yoksa catch yakalamaz
} catch (err) {
console.error(err);
return null;
}
}3. Hata Yönetimi Wrapper Fonksiyonu
// Her async çağrısında try/catch yazmak yerine wrapper kullan
async function güvenliÇalıştır(asyncFn, varsayilan = null) {
try {
return await asyncFn();
} catch (err) {
console.error("Hata:", err.message);
return varsayilan;
}
}
// Kullanım
const kullanici = await güvenliÇalıştır(
() => getUser(1),
{ ad: "Misafir" } // Hata durumunda varsayılan
);
// Go dilinden ilham alan [error, result] pattern
async function to(promise) {
try {
const result = await promise;
return [null, result];
} catch (err) {
return [err, null];
}
}
// Kullanım — try/catch'siz hata yönetimi
const [hata, kullanici2] = await to(getUser(1));
if (hata) {
console.error("Kullanıcı getirilemedi:", hata.message);
} else {
console.log("Kullanıcı:", kullanici2.ad);
}Yaygın Hatalar ve Tuzaklar
Hata 1: forEach ile await
// ❌ EN YAYGIN HATA — forEach async'i beklemez
const idler = [1, 2, 3, 4, 5];
// Bu kod HİÇBİR ŞEYİ beklemez!
idler.forEach(async (id) => {
const user = await getUser(id);
console.log(user.name);
});
console.log("Bitti!"); // Tüm getUser'lardan ÖNCE çalışır!
// ✅ for...of kullan (sıralı)
for (const id of idler) {
const user = await getUser(id);
console.log(user.name);
}
console.log("Bitti!"); // Gerçekten hepsi bittikten sonra
// ✅ Promise.all + map kullan (paralel)
const users = await Promise.all(
idler.map((id) => getUser(id))
);
users.forEach((user) => console.log(user.name));
console.log("Bitti!");Hata 2: Sıralı Await Tuzağı
// ❌ Sıralı await — gereksiz yavaşlık
async function sayfaYukle() {
const header = await getHeader(); // 200ms bekle
const sidebar = await getSidebar(); // 200ms bekle
const content = await getContent(); // 200ms bekle
const footer = await getFooter(); // 200ms bekle
// Toplam: ~800ms 😞
}
// ✅ Paralel — bağımsız istekler aynı anda
async function sayfaYukle() {
const [header, sidebar, content, footer] = await Promise.all([
getHeader(),
getSidebar(),
getContent(),
getFooter(),
]);
// Toplam: ~200ms 😊
}Hata 3: async IIFE Unutmak
// ❌ Top-level await yoksa hata alırsın
// const data = await fetch("/api/data"); // SyntaxError (CJS'de)
// ✅ IIFE (Immediately Invoked Function Expression) kullan
(async () => {
const response = await fetch("/api/data");
const data = await response.json();
console.log(data);
})();Hata 4: await'i Promise Olmayan Değerle Kullanmak
// await Promise olmayan değerle kullanılabilir — hata vermez
const sayi = await 42;
console.log(sayi); // 42
// Ama bu anlamsız — gereksiz microtask oluşturur
// JavaScript await'e verilen değeri Promise.resolve() ile sarar
// Sonuç aynı ama bir tick gecikmesi eklenir
// ✅ Promise olmayan değerlerde await kullanma
const sayi2 = 42; // Direkt ataGerçek Dünya Örneği: API İstemcisi
Tüm öğrendiklerimizi birleştiren kapsamlı bir API istemcisi:
// Modern async/await API istemcisi
class ApiClient {
constructor(baseUrl, options = {}) {
this.baseUrl = baseUrl;
this.defaultHeaders = {
"Content-Type": "application/json",
...options.headers,
};
this.timeout = options.timeout || 10000;
}
// Timeout mekanizmalı fetch
async fetchWithTimeout(url, options = {}) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
try {
const response = await fetch(url, {
...options,
signal: controller.signal,
});
return response;
} finally {
clearTimeout(timeoutId);
}
}
// Genel istek metodu
async request(endpoint, options = {}) {
const url = `${this.baseUrl}${endpoint}`;
try {
const response = await this.fetchWithTimeout(url, {
headers: { ...this.defaultHeaders, ...options.headers },
...options,
});
if (!response.ok) {
const errorBody = await response.text();
throw new ApiError(
`HTTP ${response.status}: ${response.statusText}`,
response.status,
errorBody
);
}
// 204 No Content → null döner
if (response.status === 204) return null;
return await response.json();
} catch (err) {
if (err.name === "AbortError") {
throw new ApiError(
`İstek zaman aşımına uğradı (${this.timeout}ms)`,
408
);
}
throw err;
}
}
// CRUD kısayolları
async get(endpoint) {
return this.request(endpoint);
}
async post(endpoint, data) {
return this.request(endpoint, {
method: "POST",
body: JSON.stringify(data),
});
}
async put(endpoint, data) {
return this.request(endpoint, {
method: "PUT",
body: JSON.stringify(data),
});
}
async delete(endpoint) {
return this.request(endpoint, { method: "DELETE" });
}
// Toplu istek — paralel ve hata toleranslı
async batch(istekler) {
const sonuclar = await Promise.allSettled(
istekler.map(({ method, endpoint, data }) => {
return this[method](endpoint, data);
})
);
return {
basarili: sonuclar
.filter((r) => r.status === "fulfilled")
.map((r) => r.value),
hatali: sonuclar
.filter((r) => r.status === "rejected")
.map((r) => r.reason.message),
};
}
}
// Custom Error sınıfı
class ApiError extends Error {
constructor(message, statusCode, body) {
super(message);
this.name = "ApiError";
this.statusCode = statusCode;
this.body = body;
}
}
// Kullanım
async function main() {
const api = new ApiClient("https://api.example.com", {
timeout: 5000,
headers: { Authorization: "Bearer token123" },
});
try {
// Tek istek
const kullanici = await api.get("/users/1");
console.log("Kullanıcı:", kullanici.name);
// Yeni kayıt oluştur
const yeniUrun = await api.post("/products", {
name: "JavaScript Kitabı",
price: 149.90,
});
console.log("Oluşturuldu:", yeniUrun.id);
// Toplu istek
const toplu = await api.batch([
{ method: "get", endpoint: "/products" },
{ method: "get", endpoint: "/categories" },
{ method: "get", endpoint: "/invalid-endpoint" }, // Bu hata verecek
]);
console.log(`Başarılı: ${toplu.basarili.length}`);
console.log(`Hatalı: ${toplu.hatali.length}`);
} catch (err) {
if (err instanceof ApiError) {
console.error(`API Hatası [${err.statusCode}]:`, err.message);
} else {
console.error("Beklenmeyen hata:", err);
}
}
}
main();Async Iterators (Kısaca)
ES2018'de async fonksiyonların iterator versiyonu geldi. for await...of döngüsü ile akış (stream) verilerini asenkron olarak döngüye alabilirsin:
// Async generator — sayfalı API verisi
async function* sayfalıVeriGetir(baseUrl) {
let sayfa = 1;
let devamVar = true;
while (devamVar) {
const response = await fetch(`${baseUrl}?page=${sayfa}`);
const data = await response.json();
yield data.items; // Her sayfanın verilerini yield et
devamVar = data.hasNextPage;
sayfa++;
}
}
// for await...of ile kullanım
async function tumUrunleriGetir() {
const tumUrunler = [];
for await (const urunler of sayfalıVeriGetir("/api/products")) {
tumUrunler.push(...urunler);
console.log(`${tumUrunler.length} ürün yüklendi...`);
}
console.log(`Toplam: ${tumUrunler.length} ürün`);
return tumUrunler;
}💡 İpucu: Async iterators konusu, Iterator ve Generator dersinde (Bölüm 6) daha detaylı işlenecek.
Özet
Bu derste async/await'in tüm yönlerini öğrendik:
`async` fonksiyonlar her zaman Promise döner. İçinden
throwedilen hata, rejected Promise'e dönüşür.`await` bir Promise'i bekler, fonksiyonu askıya alır ama dışarıdaki kodu bloklamaz.
Hata yönetimi `try/catch` ile yapılır — Promise chain'deki
.catch()yerine tanıdık sözdizimi kullanılır.Bağımsız asenkron işlemleri `Promise.all` ile paralel çalıştırın — sıralı await büyük performans kaybı yaratır.
`forEach` ile await çalışmaz —
for...of(sıralı) veyaPromise.all + map(paralel) kullanın.Top-level await ES Module'lerde modül seviyesinde await kullanmayı sağlar — CommonJS'te çalışmaz.
Bir sonraki derste, gerçek HTTP istekleri yapmak için kullanılan Fetch API'yi detaylıca inceleyeceğiz.
AI Asistan
Sorularını yanıtlamaya hazır