Birkaç aydan fazladır JavaScript yazıyorsanız, derin iç içe callback'lerin ve dolaşık .then() zincirlerinin acısını hissettiniz. ES2017'de tanıtılan async/await, bunların hepsini çözdü — ancak geliştiriciler hâlâ aynı üç veya dört tuzağa düşmeye devam ediyor. Her şeyi doğru bir şekilde ele alalım: nasıl çalıştığını, hataları nasıl iyi yöneteceğinizi ve performans için gerçekten önemli olan paralel yürütme kalıplarını.

Temel Bilgiler — async Fonksiyonlar ve await

Bir async fonksiyon her zaman bir Promise döndürür. İçinde, await beklenen Promise çözülene kadar yürütmeyi duraklatır. Zihinsel model bu kadardır:

js
async function fetchUserProfile(userId) {
  const response = await fetch(`https://api.example.com/users/${userId}`);
  const profile = await response.json();
  return profile; // wrapped in a Promise automatically
}

// Calling an async function gives you a Promise
const profilePromise = fetchUserProfile(42);
profilePromise.then(profile => console.log(profile.name));

// Or use await at the call site
const profile = await fetchUserProfile(42);
console.log(profile.name);

await anahtar sözcüğü yalnızca bir async fonksiyon içinde — veya bir ES modülünün üst düzeyinde kullanılabilir (buna daha sonra değineceğiz). Başka bir yerde kullanmak sözdizimi hatasıdır.

Hata Yönetimi — Doğru Yol ve Yanlış Yol

Çoğu öğreticinin raydan çıktığı yer burasıdır. İçgüdü, her şeyi try/catch ile sarmak ve işi bitirmektir. Bu işe yarar, ancak boş catch blokları gerçek hataları maskeleyen bir kod kokusu oluşturur:

js
// ❌ Don't do this — silent failure, impossible to debug
async function loadConfig() {
  try {
    const res = await fetch('/api/config');
    return await res.json();
  } catch (err) {
    // swallowed — you'll never know what broke
  }
}

// ✅ Do this — handle errors explicitly, return something meaningful
async function loadConfig() {
  try {
    const res = await fetch('/api/config');
    if (!res.ok) {
      throw new Error(`Config fetch failed: ${res.status} ${res.statusText}`);
    }
    return await res.json();
  } catch (err) {
    console.error('loadConfig error:', err.message);
    return null; // caller can check for null
  }
}

Async fonksiyonların başarısızlık durumunda null döndürdüğü veya kasıtlı hatalar fırlattığı bir kalıbı tercih ederim. Kaçındığım şey, bir hatayı yakalamak, kaydetmek ve sonra arayanın isteğin başarılı olduğunu düşünmesine neden olan bir değer döndürmektir.

Profesyonel ipucu: Bir try bloğunda birden fazla await varsa, tek bir catch hepsini yönetir — ancak hangisinin başarısız olduğuna dair bağlamı kaybedersiniz. Karmaşık akışlar için ayrı try/catch blokları veya [error, data] demetleri döndüren await to(promise) gibi bir yardımcı düşünün.

Sıralı vs Paralel — Performans Tuzağı

Üretim kodunda en sık gördüğüm hata budur. Her çağrıyı birbiri ardına await ettiğinizde, bunları sıralı olarak çalıştırırsınız — birbirinden tamamen bağımsız olsalar bile:

js
// ❌ Sequential — takes ~900ms total (300 + 300 + 300)
async function loadDashboard(userId) {
  const user     = await fetchUser(userId);       // 300ms
  const orders   = await fetchOrders(userId);     // 300ms
  const settings = await fetchSettings(userId);  // 300ms
  return { user, orders, settings };
}

// ✅ Parallel with Promise.all — takes ~300ms total
async function loadDashboard(userId) {
  const [user, orders, settings] = await Promise.all([
    fetchUser(userId),
    fetchOrders(userId),
    fetchSettings(userId)
  ]);
  return { user, orders, settings };
}

Promise.all() üç isteği aynı anda başlatır ve hepsinin tamamlanmasını bekler. Herhangi biri reddedilirse, tümü reddedilir. Bu, tüm verilerin gerekli olduğu pano tarzı yükleme için genellikle istediğiniz şeydir.

Promise.allSettled — Kısmi Başarısızlık Sorun Değilse

Bazen birden fazla istek başlatmak ve bazıları başarısız olsa bile başarıyla dönen verileri kullanmak istersiniz. Promise.allSettled() tam olarak bunun için geliştirilmiştir:

js
async function loadWidgets(widgetIds) {
  const results = await Promise.allSettled(
    widgetIds.map(id => fetchWidget(id))
  );

  const widgets = [];
  const errors  = [];

  for (const result of results) {
    if (result.status === 'fulfilled') {
      widgets.push(result.value);
    } else {
      errors.push(result.reason.message);
    }
  }

  if (errors.length > 0) {
    console.warn('Some widgets failed to load:', errors);
  }

  return widgets; // return whatever succeeded
}

Bu kalıp, kritik olmayan UI öğeleri için mükemmeldir — birden fazla bağımsız bölümü olan bir kenar çubuğu gibi. Biri başarısız olursa, tüm sayfayı boş bırakmak yerine geri kalanını gösterirsiniz.

Döngülerde async — forEach Tuzağı

Bu herkesi en az bir kez etkilemiştir. Array.forEach(), async callback'leri beklemez — onları başlatır ve hemen devam eder. Async işlerin herhangi biri tamamlanmadan önce döngü biter:

js
const orderIds = [101, 102, 103, 104];

// ❌ forEach ignores async — all requests fire in parallel uncontrolled,
// and code after the forEach runs before any complete
orderIds.forEach(async (id) => {
  await processOrder(id); // NOT awaited by forEach
});
console.log('done?'); // prints before any order is processed

// ✅ for...of — sequential, fully awaited
for (const id of orderIds) {
  await processOrder(id);
}
console.log('done'); // prints after all orders are processed

// ✅ Parallel but controlled — all fire at once, await all completions
await Promise.all(orderIds.map(id => processOrder(id)));
console.log('done'); // prints after all orders are processed

Sıra önemli olduğunda veya istekleri kısıtlamanız gerektiğinde (birer birer işleme) for...of kullanın. Maksimum paralellik istediğinizde ve sıralı garantilere ihtiyaç duymadığınızda Promise.all(map(...)) kullanın.

ES Modüllerinde Üst Düzey await

ES2022'den bu yana, bir ES modülünün üst düzeyinde await kullanabilirsiniz — sarmalayıcı fonksiyon gerekmez. Bu, async verilere bağımlı modül başlatma için büyük bir gelişmedir:

js
// config.js (ES module)
const response = await fetch('/api/runtime-config');
const config   = await response.json();

export const API_BASE_URL  = config.apiBaseUrl;
export const FEATURE_FLAGS = config.featureFlags;
js
// main.js — imports wait for config.js to fully resolve
import { API_BASE_URL, FEATURE_FLAGS } from './config.js';

console.log(API_BASE_URL); // guaranteed to be loaded

Üst düzey await, package.json'da "type": "module" ile Node.js 14.8+'da ve yerel ES modülleri aracılığıyla tüm modern tarayıcılarda çalışır. İçe aktaran modülün yürütmesi, beklenen modül tamamen çözülene kadar duraklatılır — tam olarak ihtiyaç duyduğunuz garantidir.

Gerçek Bir Pipeline — Getirme, Ayrıştırma ve Dönüştürme

İşte her şeyi birleştiren gerçekçi bir async pipeline: bir API'den veri getirme, HTTP hatalarını yönetme, veriyi dönüştürme ve başarısızlık durumunda zarif bir şekilde geri düşme:

js
async function getProductCatalog(categoryId) {
  // Step 1: fetch raw data
  const response = await fetch(
    `https://api.shop.example.com/categories/${categoryId}/products`,
    { headers: { Authorization: `Bearer ${getAuthToken()}` } }
  );

  if (!response.ok) {
    throw new Error(`Catalog fetch failed: ${response.status}`);
  }

  // Step 2: parse JSON
  const raw = await response.json();

  // Step 3: transform into the shape your UI needs
  const products = raw.items.map(item => ({
    id:       item.product_id,
    name:     item.display_name,
    price:    (item.price_cents / 100).toFixed(2),
    inStock:  item.inventory_count > 0,
    imageUrl: item.media?.[0]?.url ?? '/images/placeholder.png'
  }));

  // Step 4: filter out anything that's been discontinued
  return products.filter(p => !p.discontinued);
}

// Usage
try {
  const catalog = await getProductCatalog('electronics');
  renderProductGrid(catalog);
} catch (err) {
  showErrorBanner(`Could not load products: ${err.message}`);
}

Kullanışlı Araçlar

JSON yüklerini işleyen async kodda hata ayıklarken bu araçlar yardımcı olur: API yanıtlarını incelemek için JSON Formatter, bozuk yükleri kodunuza ulaşmadan önce yakalamak için JSON Validator, ve async fonksiyon kodunu temizlemek için JS Formatter. Tam async/await spesifikasyonu için MDN async kılavuzu en kapsamlı referanstır ve ihtiyaç duyarsanız TC39 spesifikasyonu tam semantiği kapsar.

Sonuç

async/await, asenkron JavaScript'i okunabilir kılar — ancak tuzaklar gerçektir. Ayrıştırmadan önce her zaman response.ok'u kontrol edin, catch bloklarının hataları sessizce yutmasına hiçbir zaman izin vermeyin, bağımsız paralel çağrılar için Promise.all() kullanın ve async callback'lerle forEach'ten uzak durun. Bu alışkanlıkları edindiğinizde async kodunuz hem hızlı hem de hata ayıklanabilir olacaktır.