İçeriğe geç
Educora
İleri22 dk20 / 27

Hata yönetimi ve hata ayıklama

`try...catch`'in ötesine geç: yerleşik hata tipleri, `cause` ile kendi hata sınıfların, yığın izini okumak, `debugger` ve hataları kolayca bulduran konsol metotları.

Kendini test et
Bu derste öğreneceklerin
  • Yerleşik hata tiplerini (TypeError, ReferenceError, RangeError, SyntaxError) ve genel nedenlerini tanımak
  • Kendi hata sınıflarını oluşturmak, cause eklemek ve hataları, asenkron kod dahil, tipine göre işlemek
  • Bir yığın izini okumak ve kesme noktaları, debugger ve konsol metotlarıyla hata ayıklamak

Bir kullanıcı yazıyor: “Düğme hiçbir şey yapmıyor.” Ekranda hiçbir işaret yok, ama konsolda kırmızı bir satır var: Chrome'da TypeError: Cannot read properties of undefined (reading 'price') ve altında dosya adlarıyla sayılar. Yeni başlayan için bu gürültüdür, bir profesyonel için ise doğrudan hataya götüren bir haritadır. Bu derste o haritayı okumayı ve kendi hatalarını doğru tasarlamayı öğreneceksin.

Yerleşik hata tipleri

TipGenel nedenÖrnek
TypeErroryanlış tipte bir değerle işlem: fonksiyon olmayanı çağırmak, undefined/null'un özelliğini okumaknull.name, (42)()
ReferenceErrorvar olmayan (ya da TDZ'deki) bir değişkenconsole.log(missing)
RangeErrorizin verilen aralığın dışında bir sayınew Array(-1)
SyntaxErrorayrıştırılamayan kod ya da JSONJSON.parse('{bad}')
JavaScript
const attempts = [
  () => null.name,
  () => missingVariable,
  () => new Array(-1),
  () => JSON.parse('{bad json}'),
  () => (42)(),
];

for (const attempt of attempts) {
  try {
    attempt();
  } catch (error) {
    console.log(error.name, '|', error instanceof Error);
  }
}
▸ Beklenen çıktı
TypeError | true
ReferenceError | true
RangeError | true
SyntaxError | true
TypeError | true

Kendi hataların ve cause

throw her değeri fırlatabilir, ama her zaman bir Error nesnesi fırlat; yığın izi yalnızca onda vardır. İyi bir kural: yalnızca işleyebildiğin hataları yakala, gerisini yeniden fırlat (throw error). Programının hatalarını birbirinden ayırmak için Error'dan türeyen bir sınıf hiyerarşisi kur; böylece instanceof ile her türe uygun tepki verebilirsin.

JavaScript
class AppError extends Error {
  constructor(message, options) {
    super(message, options);
    this.name = this.constructor.name;
  }
}
class ValidationError extends AppError {}
class NotFoundError extends AppError {}

function findUser(users, id) {
  if (!Number.isInteger(id)) throw new ValidationError(`Bad id: ${id}`);
  const user = users.find((u) => u.id === id);
  if (!user) throw new NotFoundError(`User ${id} not found`);
  return user;
}

const users = [{ id: 1, name: 'Aysel' }];
for (const id of [1, 'x', 7]) {
  try {
    console.log(findUser(users, id).name);
  } catch (error) {
    if (error instanceof ValidationError) console.log('400', error.message);
    else if (error instanceof NotFoundError) console.log('404', error.message);
    else throw error;
  }
}
▸ Beklenen çıktı
Aysel
400 Bad id: x
404 User 7 not found
this.constructor.name, her alt sınıfa otomatik olarak doğru adı verir. 400 ve 404 birer HTTP durum kodudur; API dersinde onlara döneceğiz.

Alt düzeydeki bir hatayı yakalayıp daha anlaşılır bir hata fırlattığında orijinalini **cause** (ES2022) özelliğinde sakla. Böylece kullanıcı net bir mesaj görür, geliştirici de gerçek nedeni kaybetmez.

JavaScript
function loadSettings(text) {
  try {
    return JSON.parse(text);
  } catch (error) {
    throw new Error('Settings file is broken', { cause: error });
  }
}

try {
  loadSettings('{ theme: dark }');
} catch (error) {
  console.log(error.message);
  console.log('Caused by:', error.cause.name);
  console.log(error.cause instanceof SyntaxError);
}
▸ Beklenen çıktı
Settings file is broken
Caused by: SyntaxError
true
JavaScript
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function saveScore(score) {
  await wait(100);
  if (score < 0) throw new RangeError('Score cannot be negative');
  return 'saved';
}

try {
  console.log(await saveScore(10));
  console.log(await saveScore(-5));
} catch (error) {
  console.log(`${error.name}: ${error.message}`);
}

const result = await saveScore(-1).catch((error) => `fallback (${error.message})`);
console.log(result);
▸ Beklenen çıktı
saved
RangeError: Score cannot be negative
fallback (Score cannot be negative)

Yığın izini okumak

Yığın izi (stack trace), hatanın oluştuğu andaki çağrı zincirini gösterir; en yenisi en üsttedir. İlk satır hatanın adı ve mesajıdır, sonraki satırlar at fonksiyon (dosya:satır:sütun) biçimindedir. Aşağıdaki cart.js dosyasında ikinci ürünün product özelliği yok. Node.js'in yazdırdığı iz şöyle (klasör yolları kısaltıldı, Node'un iç satırları çıkarıldı):

JavaScript
// cart.js
function calculateTotal(items) {
  return items.reduce((sum, item) => sum + item.product.price * item.qty, 0);
}
function renderCart(cart) {
  return calculateTotal(cart.items);
}
renderCart({ items: [{ product: { price: 2 }, qty: 1 }, { qty: 3 }] });
Beklenen çıktı
TypeError: Cannot read properties of undefined (reading 'price')
    at cart.js:3:57
    at Array.reduce (<anonymous>)
    at calculateTotal (cart.js:3:16)
    at renderCart (cart.js:6:10)
    at Object.<anonymous> (cart.js:8:1)

Hata ayıklama araçları

  1. 1
    Hatayı yeniden üret

    Hataya yol açan kesin adımları bul.

  2. 2
    Bir kesme noktası koy

    DevTools › Sources panelinde bir satır numarasına tıkla ya da koda debugger; yaz; DevTools açıkken çalışma orada durur.

  3. 3
    Değerleri incele

    Scope bölümünde değişkenlere bak, fareyi değerlerin üzerine getir, Watch'a ifadeler ekle.

  4. 4
    Adım adım ilerle

    Satır satır ilerle, bir fonksiyonun içine gir ya da ondan çık.

  5. 5
    Düzelt ve doğrula

    Düzeltmeden sonra aynı adımları tekrar uygula.

DevTools'u açF12
çalışmayı sürdürF8
sonraki satıra geç (step over)F10
fonksiyonun içine gir (step into)F11
fonksiyondan çık (step out)Shift+F11

Konsolun log dışında da kullanışlı metotları vardır. Bu sayfadaki çalıştırıcı yalnızca console.log'u gösterir; bu yüzden örneği tarayıcı konsolunda ya da Node.js'te çalıştır. Aşağıdaki çıktı Node.js'ten; sort için ölçülen süre her çalıştırmada farklıdır.

JavaScript
const students = [
  { name: 'Aysel', score: 92 },
  { name: 'Murad', score: 78 },
];

console.table(students);

console.group('Checkout');
console.log('items: 3');
console.warn('coupon expired');
console.groupEnd();

console.count('click');
console.count('click');
console.assert(students.length > 5, 'Expected more than 5 students');

console.time('sort');
students.sort((a, b) => b.score - a.score);
console.timeEnd('sort');
Beklenen çıktı
┌─────────┬─────────┬───────┐
│ (index) │ name    │ score │
├─────────┼─────────┼───────┤
│ 0       │ 'Aysel' │ 92    │
│ 1       │ 'Murad' │ 78    │
└─────────┴─────────┴───────┘
Checkout
  items: 3
  coupon expired
click: 1
click: 2
Assertion failed: Expected more than 5 students
sort: 0.027ms
Alıştırma

ValidationError sınıfını tamamla (name değeri 'ValidationError' olmalı) ve parseAge(text) fonksiyonunu yaz. Metin tam sayı değilse Not a whole number: <text>, 0–120 aralığının dışındaysa Out of range: <age> mesajlı bir ValidationError fırlat; aksi hâlde yaşı döndür.

Alıştırma · JavaScript
class ValidationError extends Error {
  // set this.name in the constructor
}

function parseAge(text) {
  // Number(text) must be an integer from 0 to 120
}

for (const input of ['16', 'abc', '130', '42']) {
  try {
    console.log('Age:', parseAge(input));
  } catch (error) {
    if (error instanceof ValidationError) {
      console.log(`${error.name}: ${error.message}`);
    } else {
      throw error;
    }
  }
}
▸ Beklenen çıktı
Age: 16
ValidationError: Not a whole number: abc
ValidationError: Out of range: 130
Age: 42
Alıştırma

retry(fn, times) fonksiyonunu yaz: fn'i başarılı olana kadar en fazla times kez çağırsın ve her başarısızlıktan sonra Retrying after: <mesaj> yazdırsın. Tüm denemeler başarısız olursa son hatayı cause içinde saklayan, All <times> attempts failed mesajlı bir hata fırlatsın.

Alıştırma · JavaScript
function createFlakyTask(failuresBeforeSuccess) {
  let calls = 0;
  return () => {
    calls++;
    if (calls <= failuresBeforeSuccess) throw new Error(`Attempt ${calls} failed`);
    return `Success on attempt ${calls}`;
  };
}

function retry(fn, times) {
  // your code here
}

console.log(retry(createFlakyTask(2), 3));
try {
  retry(createFlakyTask(5), 3);
} catch (error) {
  console.log(error.message, '| cause:', error.cause.message);
}
▸ Beklenen çıktı
Retrying after: Attempt 1 failed
Retrying after: Attempt 2 failed
Success on attempt 3
Retrying after: Attempt 1 failed
Retrying after: Attempt 2 failed
Retrying after: Attempt 3 failed
All 3 attempts failed | cause: Attempt 3 failed

Önemli noktalar

  • TypeError yanlış tip ya da undefined, ReferenceError bilinmeyen değişken, RangeError aralık dışı değer, SyntaxError ise ayrıştırılamayan kod ya da JSON demektir.
  • Mesaj metnini değil, error.name'i ya da instanceof'u kontrol et; mesajlar tarayıcıya göre değişir.
  • Kendi hata sınıfların ve cause, hataları anlamlı kılar ve asıl nedeni korur.
  • Asenkron bir hata yalnızca try içindeki await ya da .catch() ile yakalanır.
  • Yığın izini kendi dosyanın geçtiği ilk satırdan oku; tahmin etmek yerine kesme noktaları, debugger ve konsol metotlarını kullan.

Kendini test et

10 soru. Her doğru cevap XP kazandırır.

1 / 10
const user = null; user.length; hangi hatayı fırlatır?