Kontrol akışı ve hata yakalama

JavaScript, uygulamanızın etkilişim halinde olmasını sağlayan kontrol akışı ifadeleri gibi birçok ifadeyi destekler. Bu bölümde, bu ifadeler üzerine durulacaktır.

En basit olarak anlatmak gerekirse, JavaScript tarafından çalıştırılacak her komuta ifade adı verilir. Noktalı virgül (;) karakteri ise, JavaScript kodundaki ifadelerin birbirinden ayrılması için kullanılmaktadır.

Blok ifadesi

En temel ifade türü, ifadelerin gruplanmasını sağlayan blok ifadesidir. Blok ifadesi,  bir çift süslü parantezle sınırlandırılır:

{ ifade_1; ifade_2; . . . ifade_n; }

Örnek

Blok ifadeleri genellikle kontrol akışı ifadeleri ile birlikte kullanılır (örn: if, for, while).

while (x < 10) {
  x++;
}

Buradaki, { x++; } bir blok ifadesidir.

Önemli: ECMAScript2015'ten önceki JavaScript'te blok etki alanı bulunmamaktadır. Blok içerisinde yer alan değişkenlerin etki alanı, onları içeren fonksiyon veya .js dosyası kadar geniş bir alanı kapsar, ve bu değişkenler üzerinde yapılan değişiklikler bloğun ötesinde de kalıcılık gösterir. Başka bir deyişle blok ifadeleri, değişkenler için bir etki alanı oluşturmazlar. C ve Java dilinden aşina olduğunuz değişkenden bağımsız blok ifadeleri, JavaScript'te tamamıyla farklı bir davranış sergileyebilirler. Aşağıdaki örneği inceleyelim:

var x = 1;
{
  var x = 2;
}
console.log(x); // Ekrandaki çıktı: 2

Kodun çıktısı 2 olacaktır. Çünkü blok içerisindeki var x ifadesi  ile bloktan önce gelen var x ifadesi aynı etki alanı içerisindedir. Eğer üstteki kod C veya Java dilinde olsaydı, ekrandaki çıktı 1 olacaktı.

ECMAScript 6 ile birlikte gelen let tanımıyla oluşturulan değişkenler, blok seviyesinde etki alanına sahiptir. Daha fazla bilgi için let sayfasına bakınız.

Koşullu ifadeler

Belirli bir koşul sağlandığında çalışacak komutlar kümesine koşullu ifade denilir. JavaScript, iki adet koşullu ifadeyi destekler: if...else ve switch.

if...else ifadesi

Belirli bir mantıksal durum sağlandığında bir ifadenin çalıştırılması için if ifadesi kullanılır. Mantıksal durum sağlanmadığında çalıştırılacak komutlar için ise else kelimesi kullanıılabilir. Bir if ifadesi aşağıdaki şekilde oluşturulur:

if (mantıksal_durum) {
  ifade_1;
} else {
  ifade_2;
}

mantıksal_durum, true veya false değerler alabilen herhangi bir ifade olabilir. Eğer mantıksal_durum, true olursa ifade_1 çalışacak; aksi halde, ifade_2 is çalışacaktır. ifade_1 ve ifade_2 ifadeleri, çalıştırılacak herhangi bir ifade olabilir.

Birçok mantıksal durumun kontrolünün bütün bir halde yapılabilmesi için aşağıdaki şekilde else if tanımlamalarını kullanabilirsiniz.

if (mantıksal_durum_1) {
  ifade_1;
} else if (mantıksal_durum_2) {
  ifade_2;
} else if (mantıksal_durum_n) {
  ifade_n;
} else {
  ifade_son;
} 

Üstteki gibi çoklu mantıksal durumların olduğu ifadelerde,  yalnızca true olan ilk mantıksal durum çalıştırılır, ilişkili diğer kontrol ifadeleri çalıştırılmaz. Birçok ifadenin çalıştırılması için ifadeler, blok ifadesi ({ ... }) içerisinde gruplandırılır. Özellikle iç içe if ifadelerinin olduğu durumlar başta olmak üzere blok ifadeleri, geliştiriciler arasında yaygın olarak kullanılmaktadır:

if (mantıksal_durum) {
  eğer_durum_true_ise_çalışacak_ifade_1;
  eğer_durum_true_ise_çalışacak_ifade_2;
} else {
  eğer_durum_false_ise_çalışacak_ifade_3;
  eğer_durum_false_ise_çalışacak_ifade_4;
}
mantıksal_durum kısmında herhangi bir değişkene değer atamak yanlış bir kullanımdır, çünkü kodunuza sizden sonra bakan biri atama işlemini ilk bakışta eşitlik olarak görebilir. Örneğin aşağıdaki şekilde bir kullanım yanlıştır:
 
if (x = y) {
  /* diğer ifadeler */
}

Eğer mantıksal_durum kısmında gerçekten atama yapmanız gerekiyorsa, bunu yapmanın en iyi yolu atama ifadesini parantezler içerisine almaktır. Örneğin:

if ((x = y)) {
  /* diğer ifadeler */
}

Yanlışımsı (falsy) değerler

Aşağıdaki değerler JavaScript tarafından false olarak değerlendirilir ve (Falsy değerler olarak bilinir):

  • false
  • undefined
  • null
  • 0
  • NaN
  • Boş string ("")

Mantıksal durum içerisine alınan diğer bütün değerler ve nesneler, JavaScript tarafından true olarak değerlendirilir.

Boolean nesnesindeki true ve false ile ilkel tipteki true ve false değerlerini karıştırmayınız. Örneğin:

var b = new Boolean(false);
if (b) // bu ifade true olarak değerlendirilir

Örnek

Aşağıdaki örnekte bulunan checkData fonksiyonu, HTML dokümanındaki formda yer alan ikiKarakter isimli girdi nesnesine ait değerin, karakter sayısı 2 ise true döndürülür, değilse ekrana bir uyarı metni basılır ve false döndürülür:

function checkData() {
  if (document.form1.ikiKarakter.value.length == 2) {
    return true;
  } else {
    alert("Tam olarak iki karakter giriniz. " +
    document.form1.ikiKarakter.value + " geçersizdir.");
    return false;
  }
}

switch ifadesi

Bir switch ifadesine, mantıksal bir ifade verilir ve bu ifade ile eşleşen bir etiket varsa, etiketi içeren case ifadesi çalıştırılır, yoksa varsayılan ifade (default) çalıştırılır. Örnek:

switch (mantıksal_ifade) {
  case etiket_1:
    ifadeler_1
    [break;]
  case etiket_2:
    ifadeler_2
    [break;]
    ...
  default:
    varsayılan_ifadeler
    [break;]
}

Üstteki kodu çalıştırırken JavaScript, mantıksal_ifade ile eşleşen etikete sahip case cümleciğini arar ve ilişkili ifadeleri çalıştırır. Eğer eşleşen hiçbir etiket bulunamadıysa, default cümleciğinin olup olmadığına bakar, varsa ve ilgili varsayılan ifadeleri çalıştırır. Eğer default cümleciği de yoksa, switch bloğundan çıkılır.  default cümleciğinin sırası önemli olmamakla birlikte, genel kullanımlarda hep en sonda yer almaktadır.

Her case cümleciğinde, isteğe bağlı olarak konulan break ifadesi,  eşleşen ifadenin çalıştırılıp tamamlandıktan sonra switch bloğundan çıkmayı sağlar. Eğer break ifadesi yazılmazsa, program switch ifadesi içerisindeki bir sonraki case ifadesini çalıştırarak yoluna devam eder. 

Örnek

Aşaşğıdaki örnekte, meyve ifadesinin değeri "Muz" ise,  program "Muz" değeri ile eşleşen case "Muz" ifadesini çalıştırır. break ile karşılaşıldığında, program switch bloğundan çıkar ve switch bloğundan sonraki kodları çalıştırır. Eğer break ifadesi olmasaydı, "Muz" ile alakasız olan, case "Kiraz" ifadesi de çalıştırılacaktı.

switch (meyve) {
  case "Portakal":
    console.log("Portakalların kilosu ₺1.99 lira.");
    break;
  case "Elma":
    console.log("Elmaların kilosu ₺1.49 lira.");
    break;
  case "Muz":
    console.log("Muzların kilosu ₺2.49 lira.");
    break;
  case "Kiraz":
    console.log("Kirazların kilosu ₺2.19 lira.");
    break;
  case "Çilek":
    console.log("Çileklerin kilosu ₺2.49 lira.");
    break;
  case "Avokado":
    console.log("Avokadoların kilosu ₺5.99 lira.");
    break;
  default:
   console.log("Maalesef elimizde hiç " + meyve + " kalmadı.");
}
console.log("Başka bir şey lazım mı?");

Exception (Hata) yakalama ifadeleri

throw ifadesi ile exception fırlatabilir, try...catch ifadeleri kullanarak hata yakalama işlemlerinizi yürütebilirsiniz.

Exception türleri

JavaScript'te neredeyse her nesne fırlatılabilir. Buna rağmen fırlatılacak türdeki nesnelerin hepsi aynı şekilde oluşturulmamışlardır. Sayı ve string değerlerinin hata olarak fırlatılması oldukça yaygın olmasına rağmen, bu amaç için belirli olarak  oluşturulan aşağıdaki exception türlerinden birinin kullanılması daha anlamlıdır:

throw ifadesi

Bir exception fırlatmak için throw ifadesini kullanılır. Bir exception fırlattığınız zaman, fırlatılacak ifadeyi de belirlersiniz:

throw ifade;

Herhangi bir türdeki ifadeyi exception olarak fırlatabilirsiniz. Aşağıdaki kodda çeşitli türlerdeki exception'lar fırlatılmaktadır:

throw "Hata2";   // String türü 
throw 42;         // Sayı türü
throw true;       // Boolean türü
throw { toString: function() { return "Ben bir nesneyim."; } };
Not: Bir exception fırlatırken ilgili exception nesnesini belirleyebilirsiniz. Daha sonra catch bloğunda hatayı yakaladığınızda ilgili exception nesnesinin özelliklerine erişebilirsiniz. Aşağıdaki ifadede, KullanıcıHatası sınıfından bir nesne oluşturulmakta, ve throw ifadesinde bu nesne fırlatılmaktadır.
// KullanıcıHatası türünde nesne oluşturuluyor
function KullanıcıHatası(mesaj){
  this.mesaj=mesaj;
  this.adı="KullanıcıHatası";
}

// Oluşturulan KullanıcıHatası nesnesinin konsola yazıldığında güzel bir
// ifade yazılması için aşağıdaki şekilde toString() fonksiyonunu override ediyoruz.
KullanıcıHatası.prototype.toString = function () {
  return this.adı+ ': "' + this.mesaj+ '"';
}

// KullanıcıHatası nesnesi yaratılır ve exception olarak fırlatılır
throw new KullanıcıHatası("Yanlış bir değer girdiniz.");

try...catch ifadesi

try...catch ifadesi, çalıştırılması istenen ifadeleri bir blokta tutar. Fırlatılacak exception için bir veya daha fazla ifade belirlenerek, oluşacak bir try...catch ifadesi tarafından yakalanması sağlanır.

try...catch ifadesi, çalıştırılacak bir veya daha fazla komutun yer aldığı, ve try bloğu içerisinde hata oluştuğunda çalışacak ifadeleri içeren, 0 veya daha fazla catch ifadesinin yer aldığı bir try bloğu içerir. Bu şekilde, try içerisinde yer alan başarıyla çalışmasını istediğiniz kodlarda bir sorun oluştuğunda, catch bloğunda bu sorunun üstesinden gelecek kontrolleri yazabilirsiniz. Eğer try bloğu içerisindeki herhangi bir ifade (veya try bloğu içerisinden çağırılan fonksiyon) bir exception fırlatırsa, JavaScript anında catch bloğuna bakar. Eğer try bloğu içerisinde bir exception fırlatılmazsa, catch bloğu çalıştırılmaz ve atlanır. try ve catch bloklarından sonra, eğer varsa finally bloğu da çalıştırılır.

Aşağıdaki örnekte bir try...catch ifadesi kullanılmaktadır. Fonksiyonda, parametre olarak geçilen ay sayısı değeri baz alınarak, diziden ilgili ayın adı getirilmektedir. Eğer ay sayısı değeri 1-12 arasında değilse, "Geçersiz ay sayısı." değeri exception olarak fırlatılır. catch bloğundaki ayAdı değişkeni de "bilinmeyen" olarak atanır.

function getAyAdı(aySayisi) {
  aySayisi= aySayisi-1; // Dizi indeksi için aySayisi 1 azaltılır (1=Oca, 12=Ara)
  var aylar= ["Oca","Şub","Mar","Nis","May","Haz","Tem",
                "Ağu","Eyl","Eki","Kas","Ara"];
  if (aylar[aySayisi] != null) {
    return aylar[aySayisi];
  } else {
    throw "Geçersiz ay sayısı."; // burada throw ifadesi kullanılıyor
  }
}

try { // denenecek ifadeler
  ayAdı = getAyAdı(aySayim); // function could throw exception
}
catch (e) {
  ayAdı = "bilinmiyor";
  hatalarımıKaydet(e); // hataların kaydedilmesi için exception nesnesi geçiliyor.
}

catch bloğu

try bloğunda oluşturulan tüm exception'ların yakalanması için catch bloğunu kullanabilirsiniz.

catch (exceptionDeğişkeni) {
  ifadeler
}

catch bloğunda, throw ifadesi tarafından belirlenecek değerin tutulması için bir değişken tanımlanır; bu değişken kullanılarak, fırlatılan exception ile ilgili bilgiler elde edilmiş olur. catch bloğuna girildiğinde JavaScript, bu değişkenin içini doldurur; değişken değeri sadece catch bloğu süresince geçerli kalır; catch bloğu çalışmasını tamamladığında değişken artık mevcut değildir.

Örneğin aşağıdaki kodda, bir exception fırlatılıyor, ve fırlatıldığı anda otomatik olarak catch bloğuna iletiliyor.

try {
  throw "hata" // bir exception oluşturur.
}
catch (e) {
  // herhangi bir exception'ı yakalamak için oluşturulan ifadeler
  hatalarımıKaydet(e) // hataların kaydedilmesi için exception nesnesi geçilir.
}

finally bloğu

finally bloğu, try...catch ifadesinden sonra çalıştırılacak kod satırlarını içerir. finally bloğu, hata olsun veya olmasın çalışır. Eğer hata olmuşsa ve exception fırlatılmışsa, bu hatayı karşılayacak catch bloğu olmasa dahi finally bloğu çalışır. 

finally bloğu, hata oluştuğunda bu hataya sebep olan değişkenin kullandığı kaynakların sisteme geri verilmesi için en iyi yerdir. Bu şekilde hata tüm ayrıntılarıyla çözülmüş olur. Aşağıdaki örnekte bir dosya açılmakta, ve sonrasında dosyaya yazma işlemleri için kullanan ifadeler çalıştırılmaktadır (Sunucu taraflı yazılmış koddur. İstemci tarafında dosyaya yazma işlemleri güvenlik açısından engellenmiştir. Eğer dosya, yazmak için açılırken bir exception fırlatılırsa, kod hata vermeden önce finally bloğu çalışır ve erişilecek dosyayı kapatır.

dosyaAç();
try {
  dosyayaYaz(veriler); // Bu kısım hata verebilir
} catch(e) {  
  hatayıKaydetveGöster(e); // Hata ile ilgili bilgiler kaydedilir ve kullanıcıya bir hata mesajı sunulur.
} finally {
  dosyayıKapat(); // Dosyayı kapatır (hata olsa da olmasa da).
}

Eğer finally bloğu bir değer geri döndürürse,  bu değer, try ve catch içerisindeki return ifadelerine bakmaksızın, try-catch-finally ifadesinin tamamı için geri dönüş değeri haline gelir:

function f() {
  try {
    console.log(0);
    throw "hata";
  } catch(e) {
    console.log(1);
    return true; // Buradaki return ifadesi,
                 // finally bloğu tamamlanana dek duraklatılır.
    console.log(2); // Üstteki return ifadesinden dolayı çalıştırılmaz.
  } finally {
    console.log(3);
    return false; // catch kısmındaki return ifadesinin üstüne yazar ve geçersiz hale getirir.
    console.log(4); // return'den dolayı çalıştırılmaz.
  }
  // Şimdi "return false" ifadesi çalıştırılır ve fonksiyondan çıkılır.  
  console.log(5); // Çalıştırılmaz.
}
f(); // Konsola 0 1 3 yazar ve false değerini döndürür.

finally bloğunun, geri dönüş değerlerinin üstüne yazma etkisi, aynı zamanda catch bloğu içerisindeki exception'lar için de aynı şekilde çalışır:

function f() {
  try {
    throw "hata";
  } catch(e) {
    console.log('İçerideki "hata" yakalandı.');
    throw e; // Burası finally bloğu tamamlanana dek duraklatılır.
  } finally {
    return false; // Önceki "throw" ifadesinin üstüne yazar ve 
                  //  throw ifadesini geçersiz hale getirir.
  }
  // Şimdi "return false" ifadesi çalıştırılır.
}

try {
  f();
} catch(e) {
  // f() fonksiyonundaki throw ifadesi geçersiz hale geldiği için
  //  buradaki catch bloğu çalıştırılmaz.
  console.log('Diğer "hata" yakalandı.');
}

// Ekran çıktısı: İçerideki "hata" yakalandı.

İçiçe try...catch ifadeleri

Bir veya daha fazla iç içe try...catch ifadeleri tanımlayabilirsiniz. Eğer içteki try...catch ifadesinin catch bloğu yoksa, bir dıştaki try...catch ifadesinin catch bloğu kontrol edilir.

Error nesnelerinin etkili kullanımı

Error nesnesinin türüne bağlı olarak,  'name' ve 'message' özellikleri vasıtasıyla daha anlamlı hata mesajları tanımlayabilirsiniz. 'name' özelliği, oluşan hatayı sınıflandırır (örn, 'DOMException' veya 'Error'). 'message' ise hata nesnesinin string'e dönüştürülmesine nazaran  genellikle daha kısa bir mesaj sunulmasına olanak tanır.

Eğer kendi exception nesnelerinizi fırlatıyorsanız ve bu özellikleri kullanarak hatayı anlamlı hale getirmek istiyorsanız Error sınıfının yapıcısının getirdiği avantajlardan faydalanabilirsiniz (örneğin catch bloğunuzun, kendi exception'larınız ile sistem exception'ları arasındaki farkı ayırt edemediği gibi durumlarda). Aşağıdaki örneği inceleyelim:

function hatayaMeyilliFonksiyon() {
  if (hataVerenFonksiyonumuz()) {
    throw (new Error('Hata oluştu'));
  } else {
    sistemHatasıVerenFonksiyon();
  }
}

try {
  hatayaMeyilliFonksiyon();
}
catch (e) {
  console.log(e.name); // 'Error' yazar
  console.log(e.message); // 'Hata oluştu' veya bir JavaScript hata mesajı yazar
}

Promise nesneleri

ECMAScript2015 ile birlikte JavaScript'e, asenkron ve geciktirilmiş işlemlerin akış kontrolünün sağlanması için Promise nesneleri gelmiştir.

Bir Promise nesnesi aşağıdaki durumlardan birinde bulunur:

  • pending (bekliyor): başlangıç durumu, henüz çalıştırılmadı.
  • fulfilled (başarıyla çalıştı): başarılı işlem
  • rejected (hatalı çalıştı): hatalı işlem.
  • settled (yerleşti): Promise nesnesi başarıyla veya hatalı olarak çalıştı.

XHR ile resim yükleme

Promise ve XMLHttpRequest kullanarak bir resmin yüklenmesi için MDN GitHub promise-test sayfasında basit bir örnek mevcuttur. Ayrıca örneği canlı olarak da görebilirsiniz. Örnekteki her aşamaya yorum satırları eklenmiştir. Bu sayede Promise ve XHR mimarisini daha yakından izleyebilirsiniz. Aşağıda Promise nesnesinin akışını gösteren örneğin belgelendirilmemiş sürümü bulunmaktadır:

function resimYükle(url) {
  return new Promise(function(başarıSonucundaFonk, hataSonucundaFonk) {
    var istek = new XMLHttpRequest();
    istek.open('GET', url);
    istek.responseType = 'blob';
    istek.onload = function() {
      if (istek.status === 200) {
        başarıSonucundaFonk(istek.cevabı);
      } else {
        hataSonucundaFonk(Error('Resim yüklenemedi; hata kodu:' 
                     + istek.hataKodu));
      }
    };
    istek.onerror = function() {
      hataSonucundaFonk(Error('Bir bağlantı hatası oluştu.'));
    };
    istek.send();
  });
}

Daha fazla detaylı bilgi için Promise sayfasına bakınız.

Document Tags and Contributors

 Contributors to this page: ozcanzaferayan, pasalog, teoli, onderomega
 Last updated by: ozcanzaferayan,