İçeriğe geç
JavaScript

JavaScript Error Handling: try/catch ve Custom Error'lar

Tarık Tunç
JavaScript Error Handling: try/catch ve Custom Error'lar

JavaScript Error Handling: try/catch ve Custom Error'lar

Hata yönetimi, güvenilir ve sağlam uygulamalar geliştirmenin temel taşıdır. JavaScript'te try/catch/finally bloğu, Error sınıfı hiyerarşisi, custom error türleri ve asenkron hata yönetimi pattern'leri ile uygulamalarınızı beklenmedik durumlara karşı koruyabilirsiniz. Bu yazıda hata yönetiminin tüm yönlerini kapsamlı örneklerle inceliyoruz.

1. try/catch/finally Temelleri

try bloğu hata oluşabilecek kodu sarar, catch hatayı yakalar ve finally her durumda çalışır.

// Temel hata yakalama
try {
  const veri = JSON.parse('{ geçersiz json }');
} catch (hata) {
  console.error("JSON parse hatası:", hata.message);
  // "JSON parse hatası: Expected property name or '}' ..."
} finally {
  console.log("Bu blok her zaman çalışır");
}

// Hata türüne göre işleme
try {
  bilinmeyenFonksiyon();
} catch (hata) {
  if (hata instanceof ReferenceError) {
    console.error("Tanımsız değişken/fonksiyon:", hata.message);
  } else if (hata instanceof TypeError) {
    console.error("Tür hatası:", hata.message);
  } else if (hata instanceof SyntaxError) {
    console.error("Sözdizimi hatası:", hata.message);
  } else {
    console.error("Bilinmeyen hata:", hata);
    throw hata; // Bilinmeyen hataları yeniden fırlat
  }
}

// finally ile kaynak temizleme
function dosyaIsle(dosyaYolu) {
  let dosya = null;
  try {
    dosya = dosyaAc(dosyaYolu);
    const icerik = dosya.oku();
    return isleIcerik(icerik);
  } catch (hata) {
    console.error("Dosya işleme hatası:", hata.message);
    return null;
  } finally {
    // Hata olsun olmasın dosyayı kapat
    if (dosya) dosya.kapat();
  }
}

2. Custom Error Sınıfları

Yerleşik Error sınıfını genişleterek uygulamanıza özgü hata türleri oluşturabilirsiniz. Bu, hata türüne göre farklı davranışlar sergilemenizi kolaylaştırır.

// Temel custom error
class UygulamaHatasi extends Error {
  constructor(mesaj, kod) {
    super(mesaj);
    this.name = "UygulamaHatasi";
    this.kod = kod;
    this.tarih = new Date();
  }
}

// Spesifik hata türleri
class DogrulamaHatasi extends UygulamaHatasi {
  constructor(alanlar) {
    super("Doğrulama hatası", "VALIDATION_ERROR");
    this.name = "DogrulamaHatasi";
    this.alanlar = alanlar;
  }
}

class YetkilendirmeHatasi extends UygulamaHatasi {
  constructor(mesaj = "Yetkisiz erişim") {
    super(mesaj, "AUTH_ERROR");
    this.name = "YetkilendirmeHatasi";
    this.statusCode = 401;
  }
}

class BulunamadiHatasi extends UygulamaHatasi {
  constructor(kaynak, id) {
    super(`${kaynak} bulunamadı: ${id}`, "NOT_FOUND");
    this.name = "BulunamadiHatasi";
    this.statusCode = 404;
    this.kaynak = kaynak;
    this.kaynakId = id;
  }
}

// Kullanım
function kullaniciGetir(id) {
  const kullanici = veritabani.bul(id);
  if (!kullanici) {
    throw new BulunamadiHatasi("Kullanici", id);
  }
  return kullanici;
}

try {
  const kullanici = kullaniciGetir(999);
} catch (hata) {
  if (hata instanceof BulunamadiHatasi) {
    console.log(`${hata.kaynak} #${hata.kaynakId} bulunamadı`);
  } else if (hata instanceof YetkilendirmeHatasi) {
    console.log("Lütfen giriş yapın");
  }
}
İpucu: Custom error sınıflarında this.name özelliğini sınıf adı ile eşleştirin. Bu, hata mesajlarında ve loglarda hata türünün kolayca tanınmasını sağlar. Error.captureStackTrace (V8) ile stack trace'i de temizleyebilirsiniz.

3. Asenkron Hata Yönetimi

Asenkron kodda hata yönetimi, senkron koddan farklı yaklaşımlar gerektirir. Promise rejection'ları ve async/await hataları için farklı stratejiler vardır.

// async/await ile hata yönetimi
async function apiIstegi(endpoint) {
  try {
    const yanit = await fetch(endpoint);

    if (!yanit.ok) {
      const hataDetay = await yanit.json().catch(() => ({}));
      throw new UygulamaHatasi(
        hataDetay.mesaj || `HTTP Hatası: ${yanit.status}`,
        `HTTP_${yanit.status}`
      );
    }

    return await yanit.json();
  } catch (hata) {
    if (hata instanceof UygulamaHatasi) throw hata;

    // Ağ hatası
    if (hata.name === "TypeError" && hata.message.includes("fetch")) {
      throw new UygulamaHatasi("Ağ bağlantısı yok", "NETWORK_ERROR");
    }

    throw hata;
  }
}

// Global unhandled rejection yakalama
window.addEventListener("unhandledrejection", (event) => {
  console.error("Yakalanmamış Promise hatası:", event.reason);
  event.preventDefault(); // Varsayılan konsol hatasını engelle
  hataRaporla(event.reason);
});

// Global error yakalama
window.addEventListener("error", (event) => {
  console.error("Yakalanmamış hata:", event.error);
  hataRaporla(event.error);
});

// Retry pattern
async function yenidenDene(fn, { maxDeneme = 3, gecikme = 1000 } = {}) {
  for (let deneme = 1; deneme <= maxDeneme; deneme++) {
    try {
      return await fn();
    } catch (hata) {
      if (deneme === maxDeneme) throw hata;

      console.warn(`Deneme ${deneme} başarısız, ${gecikme}ms sonra tekrar...`);
      await new Promise(r => setTimeout(r, gecikme * deneme));
    }
  }
}

const veri = await yenidenDene(() => apiIstegi("/api/data"), {
  maxDeneme: 3,
  gecikme: 2000
});

4. Error Boundary Pattern'i

// Fonksiyonel error boundary
function guvenliCalistir(fn, varsayilan = null) {
  try {
    const sonuc = fn();
    // Promise ise asenkron hatayı da yakala
    if (sonuc instanceof Promise) {
      return sonuc.catch(hata => {
        console.error("Asenkron hata:", hata);
        return varsayilan;
      });
    }
    return sonuc;
  } catch (hata) {
    console.error("Senkron hata:", hata);
    return varsayilan;
  }
}

const sonuc = guvenliCalistir(() => riskliIslem(), "varsayılan");

// Merkezi hata yönetimi
class HataYoneticisi {
  #isleyiciler = new Map();

  kaydet(HataSinifi, isleyici) {
    this.#isleyiciler.set(HataSinifi, isleyici);
    return this;
  }

  isle(hata) {
    for (const [Sinif, isleyici] of this.#isleyiciler) {
      if (hata instanceof Sinif) {
        return isleyici(hata);
      }
    }
    // Varsayılan işleyici
    console.error("İşlenemeyen hata:", hata);
    throw hata;
  }
}

const hataYoneticisi = new HataYoneticisi()
  .kaydet(DogrulamaHatasi, (h) => ({ status: 400, alanlar: h.alanlar }))
  .kaydet(YetkilendirmeHatasi, (h) => ({ status: 401, mesaj: h.message }))
  .kaydet(BulunamadiHatasi, (h) => ({ status: 404, mesaj: h.message }));
Uyarı: Hiçbir zaman hataları sessizce yutmayın (catch(e) {}). En azından loglayın. Yakalanmamış hatalar için global handler'lar ekleyin ve production ortamında hata raporlama servisi kullanın.
Bilgi: ES2025 ile gelen Error.isError() metodu, bir değerin gerçek bir Error nesnesi olup olmadığını güvenilir şekilde kontrol etmenizi sağlar. instanceof farklı realm'lar (iframe, worker) arasında çalışmazken, Error.isError() her yerde çalışır.

Sonuç

Etkili hata yönetimi, güvenilir JavaScript uygulamalarının temelini oluşturur. try/catch/finally ile temel hata yakalama, custom error sınıfları ile anlamlı hata hiyerarşisi, asenkron hata yönetimi pattern'leri ve merkezi hata işleme stratejileri ile uygulamalarınızı dayanıklı hale getirebilirsiniz. Hataları anlamlı mesajlarla fırlatmayı, türe göre ayırt etmeyi ve kullanıcıya uygun geri bildirim vermeyi unutmayın.

Kaynaklar

İlgili Yazılar