JavaScript'te hata yönetimi, throw ile bir istisna fırlatmak ve onu try...catch ile yakalamak üzerine kuruludur. try bloğunda bir hata oluştuğunda kontrol hemen catch bloğuna geçer; finally bloğu ise hata olsa da olmasa da en sonda çalışır. Fırlatılan değer olarak her zaman bir Error nesnesi (veya TypeError, RangeError gibi bir alt sınıfı) kullanmanız önerilir.

Bu yazıda try/catch/finally kurallarını, yerleşik hata türlerini, hatayı sarmalarken kökeni korumayı sağlayan error.cause özelliğini ve kendi hata sınıflarınızı yazmayı anlatıyoruz. Asenkron koddaki hatalar için async/await yazımızı, konsolda gördüğünüz mesajların anlamı için sık hata mesajları rehberini tamamlayıcı olarak okuyabilirsiniz.

try, catch ve finally

try, catch ve finally

try ifadesi her zaman bir try bloğuyla başlar ve ardından en az bir catch veya finally bloğu gelmelidir. Böylece üç biçim ortaya çıkar: try...catch, try...finally ve try...catch...finally. if'ten farklı olarak bu bloklar süslü parantezsiz tek bir ifade olamaz.

javascript
function bol(a, b) {
  if (b === 0) throw new RangeError('Bölen sıfır olamaz');
  return a / b;
}

try {
  console.log(bol(10, 2)); // 5
  console.log(bol(1, 0));
  console.log('buraya gelinmez');
} catch (e) {
  console.log(e.name, '-', e.message); // RangeError - Bölen sıfır olamaz
} finally {
  console.log('finally her durumda çalışır');
}

Hata try bloğunun doğrudan içinde değil, oradan çağrılan bir fonksiyonun içinde fırlatılsa bile aynı catch tarafından yakalanır. İç içe try ifadelerinde içteki bloğun catch'i yoksa dıştakinin catch'i devreye girer.

catch bağlamı: değişken, destructuring ve parametresiz catch

catch (e) içindeki e fırlatılan değeri tutar ve yalnızca catch bloğu içinde geçerlidir. Tek bir isim yerine destructuring kalıbı da kullanabilirsiniz. Hata değerine ihtiyacınız yoksa parantezleri tamamen atlayabilirsiniz:

javascript
try {
  throw new TypeError('oops');
} catch ({ name, message }) {
  console.log(name, message); // TypeError oops
}

function gecerliJSON(metin) {
  try {
    JSON.parse(metin);
    return true;
  } catch {
    return false;
  }
}
console.log(gecerliJSON('[1,2]'), gecerliJSON('[1,')); // true false

Destructuring sözdizimi için destructuring ve spread yazımıza, JSON ayrıştırma hataları için JSON.parse ve stringify rehberine bakabilirsiniz.

finally bloğu nasıl çalışır?

finally bloğu, kontrol try...catch...finally yapısından çıkmadan önce mutlaka çalışır. try veya catch içinde return, throw, break ya da continue varsa bunların etkisi finally bitene kadar ertelenir. Dönüş değeri finally'den önce hesaplanır ama fonksiyon ancak finally bittikten sonra döner.

javascript
function f() {
  try {
    return 'try';
  } finally {
    console.log('finally önce çalışır');
  }
}
console.log(f());
// finally önce çalışır
// try
javascript
function g() {
  try {
    throw new Error('kaybolacak');
  } finally {
    return 'finally kazandı';
  }
}
console.log(g()); // finally kazandı

throw: neyi fırlatmalısınız?

throw herhangi bir ifadeyi fırlatabilir: metin, sayı, nesne. Ancak MDN, pratikte her zaman bir Error nesnesi ya da alt sınıfı fırlatmanızı önerir, çünkü hatayı yakalayan kod message, name ve yığın izi gibi özellikleri bekler. Düz metin fırlatırsanız bu özellikler olmaz:

javascript
try {
  throw 'sadece metin';
} catch (e) {
  console.log(typeof e, e.message); // string undefined
}

Bir sözdizimi ayrıntısı: throw anahtar sözcüğü ile fırlatılan ifade arasında satır sonu olamaz. throw yazıp ifadeyi alt satıra geçirirseniz otomatik noktalı virgül ekleme (ASI) devreye girer ve kod geçersiz hale gelir.

Yerleşik hata türleri

Yerleşik hata türleri: TypeError, ReferenceError, RangeError, SyntaxError

Genel Error yapıcısının yanında JavaScript'in çeşitli yerleşik hata türleri vardır. Hepsi name ve message özelliklerine sahiptir. Aşağıdaki kod her birinin gerçek bir örneğini tetikler:

javascript
const denemeler = [
  () => null.ad,                 // null üzerinde özellik okuma
  () => tanimsizDegisken,        // tanımlanmamış değişken
  () => new Array(-1),           // geçersiz dizi uzunluğu
  () => JSON.parse('{bozuk'),    // geçersiz JSON
  () => decodeURIComponent('%'), // bozuk URI
];
for (const f of denemeler) {
  try { f(); } catch (e) { console.log(e.constructor.name); }
}
// TypeError
// ReferenceError
// RangeError
// SyntaxError
// URIError
TürNe zaman oluşur? (MDN)
TypeErrorDeğer veya parametre beklenen türde değil
ReferenceErrorGeçersiz bir referansa erişim
RangeErrorSayısal değer geçerli aralığın dışında
SyntaxErrorSözdizimi hatası (JSON.parse dahil)
URIErrorencodeURI veya decodeURI geçersiz parametre aldı
EvalErroreval() ile ilgili hata
AggregateErrorBirden fazla hata tek nesnede, örneğin Promise.any

Bir Error nesnesinde neler var?

Her hata nesnesinin message özelliği yapıcıya verdiğiniz metni, name özelliği ise türün adını tutar. toString() bu ikisini "ad: mesaj" biçiminde birleştirir. stack özelliği MDN'de standart dışı olarak işaretlidir ama Node.js dahil yaygın motorlarda bir metin olarak bulunur. Alt sınıflar hem kendi türlerinin hem de Error'ın örneğidir:

javascript
const e = new RangeError('Değer çok büyük');
console.log(e.name); // RangeError
console.log(e.message); // Değer çok büyük
console.log(e.toString()); // RangeError: Değer çok büyük
console.log(e instanceof RangeError, e instanceof Error); // true true
console.log(typeof e.stack); // string

MDN ayrıca Firefox'a özgü, standart dışı bir InternalError türünden söz eder. Belirli bir türü yakalamak için instanceof kullanın; tanımadığınız hataları yeniden fırlatın (throw e), böylece üst katman onları görebilir.

error.cause: hatayı sarmalarken kökeni korumak

Düşük seviyeli bir hatayı yakalayıp daha anlamlı bir mesajla yeniden fırlatmak yaygın bir kalıptır. Sorun, orijinal hatanın ve yığın izinin kaybolmasıdır. Error yapıcısının ikinci parametresindeki cause seçeneği bunu çözer: orijinal hata yeni hatanın cause özelliğinde saklanır.

javascript
function ayarOku(metin) {
  try {
    return JSON.parse(metin);
  } catch (err) {
    throw new Error('Ayar dosyası okunamadı', { cause: err });
  }
}

try {
  ayarOku('{port: 80}');
} catch (e) {
  console.log(e.message); // Ayar dosyası okunamadı
  console.log(e.cause instanceof SyntaxError); // true
}

cause herhangi bir türde olabilir. MDN, kütüphane yazarlarının hata mesajını ayrıştırmak yerine makinenin okuyabileceği yapılandırılmış veriyi cause olarak vermesini önerir. cause seçeneği verilmezse özellik hiç oluşturulmaz:

javascript
const e = new Error('Zaman aşımı', { cause: { kod: 'E_ZAMAN', sure: 5000 } });
console.log(e.cause.kod); // E_ZAMAN
console.log('cause' in new Error('yok')); // false

Özel hata sınıfları yazmak

Error sınıfını genişleterek kendi hata türlerinizi tanımlayabilir, onları instanceof ile ayırt edebilirsiniz. cause desteğini korumak için options parametresini super()'e iletmeyi unutmayın. name özelliğini ayarlamak, hatanın metne çevrildiğinde doğru adla görünmesini sağlar.

javascript
class DogrulamaHatasi extends Error {
  constructor(alan, message, options) {
    super(message, options);
    this.name = 'DogrulamaHatasi';
    this.alan = alan;
  }
}

function kaydet(kullanici) {
  if (!kullanici.eposta) throw new DogrulamaHatasi('eposta', 'E-posta zorunlu');
  return 'kaydedildi';
}

try {
  kaydet({ ad: 'Can' });
} catch (e) {
  if (e instanceof DogrulamaHatasi) {
    console.log(`${e.name}: ${e.alan} alanı - ${e.message}`);
  } else {
    throw e;
  }
}
// DogrulamaHatasi: eposta alanı - E-posta zorunlu

console.log(String(new DogrulamaHatasi('x', 'deneme'))); // DogrulamaHatasi: deneme
console.log(Error.isError(new DogrulamaHatasi('x', 'y'))); // true
console.log(Error.isError({ name: 'Error', message: 'sahte' })); // false

Error.isError(), bir değerin gerçekten Error olup olmadığını denetler. MDN'e göre instanceof Error'dan daha sağlamdır: başka bir realm'de (örneğin bir iframe) oluşturulan hataları da tanır, prototipi taklit edilmiş sahte nesneleri ise reddeder. Görece yeni bir yöntem olduğu için MDN uyumluluk tablosuna bakın. Sınıf sözdizimi için class yazımıza göz atabilirsiniz.

Asenkron kodda hata yakalamak

try...catch yalnızca kendi bloğu çalışırken oluşan hataları yakalar. Bir callback daha sonra (örneğin setTimeout ile) çalışıp hata fırlatırsa o anda try bloğu çoktan bitmiştir:

javascript
try {
  setTimeout(() => { throw new Error('geç'); }, 0);
} catch (e) {
  console.log('buraya hiç gelinmez');
}
process.on('uncaughtException', (e) => console.log('global yakalandı:', e.message));
// global yakalandı: geç

Promise tabanlı kodda hatayı catch() ile, async fonksiyonlarda ise await ile birlikte try...catch kullanarak yakalarsınız. Bu konuyu Promise ve async/await yazılarında anlattık. Birden fazla Promise'in hatasını toplayan AggregateError için Promise.any karşılaştırmasına bakın.

Sık yapılan hatalar

Sık yapılan hatalar: Hatayı yutmak, Metin fırlatmak, Yeniden fırlatırken kökeni kaybetmek, finally içinde return
  • Hatayı yutmak: boş bir catch {} sorunu gizler. En azından kaydedin veya tanımadığınız hatayı yeniden fırlatın.
  • Metin fırlatmak: throw 'hata' yerine throw new Error('hata') kullanın.
  • Yeniden fırlatırken kökeni kaybetmek: { cause: err } seçeneğini ekleyin.
  • finally içinde return: önceki dönüşü ve hatayı ezer.
  • Mesaj metnine göre dallanmak: mesajlar değişebilir; özel sınıf veya yapılandırılmış cause kullanın.
  • Asenkron callback'i dıştaki try ile korumaya çalışmak: hata o blok bittikten sonra oluşur.

Sık Sorulan Sorular

catch bloğu hangi hataları yakalar?

try bloğu çalışırken, doğrudan veya çağrılan fonksiyonlar içinde fırlatılan her değeri yakalar. Sözdizimi hataları (kod hiç ayrıştırılamıyorsa) ve sonradan çalışan asenkron callback'lerdeki hatalar bu kapsama girmez.

Belirli bir hata türünü nasıl yakalarım?

JavaScript'te koşullu catch sözdizimi yoktur. Tek bir catch içinde instanceof ile türü kontrol edin ve eşleşmeyen hataları throw e ile yeniden fırlatın.

error.stack standart mı?

MDN, stack özelliğini standart dışı olarak işaretler; yine de yaygın motorlarda bulunur. Biçimi motora göre değişebileceği için ayrıştırmaya dayanan kod yazmayın.

cause ile AggregateError arasındaki fark nedir?

cause tek bir hata noktasına bağlam ekler. AggregateError aynı işlemde oluşan birbirinden bağımsız birden fazla hatayı bir arada tutar. MDN ayrıca bir hatayı ele alırken ikinci bir hata oluştuğunda kullanılan SuppressedError türünden de söz eder.

Parametresiz catch ne zaman uygun?

Hatanın kendisiyle hiçbir şey yapmayacağınız, yalnızca "başarılı mı, değil mi" sorusunun önemli olduğu durumlarda uygundur. MDN'deki JSON.parse ile geçerlilik denetimi buna iyi bir örnektir. Hatanın ayrıntısını kaydetmeniz gerekiyorsa değişkeni yazın; parametresiz catch hatayı tamamen görmezden gelir.

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 · Sık hata mesajları · async/await · Promise nedir · Class sınıflar · Strict mode