Programlama konusunda ne kadar iyi olursak olalım, bazen kodlarımızda hatalar olabilir. Bu hatalar bizim hatalarımızdan, beklenmedik bir kullanıcı girdisinden, hatalı bir sunucu yanıtından ve daha binlerce nedenden kaynaklanabilir.
Genelde kodda bir hata olduğunda yazdığımız kod bir adım ileriye gidemeden sona erer ve konsola bunun nedenini yazar.
Ancak try…catch sözdizimi yapısı hataları “yakalamamızı” sağlar, böylece kodun ölmesi yerine daha makul bir şey yaptırabiliriz.
“try…catch” yazımı
try...catch yapısı iki ana bloktan oluşur: try (dene) ve sonrasında catch (yakala):
try {
// kod...
} catch (err) {
// hata yönetimi.
}
Şu şekilde çalışır:
- Önce
try {...}içerisindekiler çalıştırılır. - Eğer hata yoksa
catch(err)görmezden gelinir: çalışma try’ın sonuna ulaşır ve sonracatch’i atlar. - Eğer hata meydana gelirse,
try’ın çalışması durdurulur vecatch(err)çalışmaya başlar. Buradakierrdeğişkeni "ne oldu da hata meydana geldi"ye dair detayları tutan bir objedir.
Öyleyse try {...} içerisindeki kod doğrudan sona eremez, bize catch içerisinde bunu idare etmemiz için olanak sağlar.
Birkaç örnek inceleyelim.
-
Hatasız örnek:
alert(1)ve(2)'yi gösterir:try { alert("try başladı"); // (1) <-- // ...no errors here alert("try bitti"); // (2) <-- } catch (err) { alert("Catch görmezden gelindi çünkü bir hata meydana gelmedi."); // (3) } alert("...Kod normal çalışmasına devam etti."); -
Hatalı örnek:
(1)ve(3)'ü gösterir:try { alert('try başladı'); // (1) <-- lalala; // hata, değişken tanımlı değil! alert('try bitti (Hiç erişilemedi)'); // (2) } catch(err) { alert(`Hata meydana geldi!`); // (3) <-- } alert("...Kod normal çalışmasına devam etti.");
try...catchsadece çalışma zamanlı hatalar içindirEğer kod yazımsal olarak hatalıysa çalışmayacaktır, örneğin süslü parantezler açılmış ama kapatılmamışsa:
try {
{{{{{{{{{{{{
} catch(e) {
alert("JavaScript motoru bunu anlayamaz, çünkü geçerli bir kod değildir.");
}
JavaScript motoru önce kodu okur, sonra çalıştırır. Eğer hata okuma aşamasında meydana gelirse bunlara “ayrıştırma-zamanı” hataları denir ve kurtarılamaz hatalardır. Bundan dolayı JavaScript motoru bunları anlayamaz.
Bundan dolayı try...catch ancak ve ancak geçerli kodlarda oluşacak hataları idare edebilir. Bu hatalara “çalışma zamanı hataları” veya bazen “istisnalar” (exception) denilmektedir.
try...catch Senkronize olarak çalışmaktadırEğer “zamanlanmış” bir kodda, setTimeout gibi, bir hata meydana gelirse try...catch bunu yakalayamaz:
try {
setTimeout(function() {
noSuchVariable; // kod burada ölecektir.
}, 1000);
} catch (e) {
alert( "çalışmaz" );
}
Bunun nedeni try...catch’in aslında fonksiyonu zamanlayan setTimeout’u kapsamasıdan dolayıdır. Fakat fonksiyon daha sonra çalışır. O anda aslında motor try...catchi geçmiş olur.
Eğer zamanlanmış fonksiyon içerisinde bu hatayı yakalamak istiyorsanız, try...catch bloğunu fonksiyonun içerisine yazmalısınız:
setTimeout(function() {
try {
noSuchVariable; // try...catch hataları yakalayacaktır.
} catch (e) {
alert( "hata burada yakalandı!" );
}
}, 1000);
Hata Objesi
Hata meydana geldiğinde, JavaScript bu hata ile ilgili bir obje yaratır. Sonrasında bu obje catch’e argüman olarak gönderilir:
try {
// ...
} catch (err) {
// <-- the "error object", could use another word instead of err
// ...
}
Tüm varsayılan hatalar için, catch içerisinde hata objesi iki ana özelliği taşır:
isim(name)- Hata ismi. Tanımsız değerler için bu
"ReferenceError"'dur. mesaj(message)- Hatanın detayları hakkında anlaşılır bilgi verir.
Çoğu ortamda standart olmayan başka özellikler de bulunmaktadır. Bunlardan en fazla kullanılan ve desteklenen:
stack- O anki çağrı yığını: Hataya neden olan fonksiyon zincirini belirtir. Genelde hata ayıklama amacıyla kullanılır.
Örneğin:
try {
lalala; // hata, değişken tanımlı değil!
} catch(err) {
alert(err.name); // ReferenceError
alert(err.message); // lalala tanımlı değil
alert(err.stack); // ReferenceError: lalala şurada tanımlanmadı ...
// ayrıca hatayı tümüyle göstermek de mümkündür.
// hata karakter dizisine "name:message" gibi çevirildi.
alert(err); // ReferenceError: lalala tanımlı değil
}
try...catch kullanımı
Gerçek hayatta try...catch’in nasıl kullanılabileceğine bakalım.
Bildiğiniz gibi, JavaScript JSON.parse(str) metodu sayesinde JSON olarak tanımlanmış değerlerin okunmasına olanak tanır.
Genelde ağ üzerinden başka bir serverdan veya kaynaktan gelen verinin okunmasında kullanılır.
Bu veriyi aldıktan sonra JSON.parse ile şu şekilde okuyabiliriz:
let json = '{"name":"John", "age": 30}'; // sunucudan gelen veri.
let user = JSON.parse(json); // bu veriyi JS objesine dönüştür.
//Artık user karakter dizisinden oluşan objelere sahiptir.
alert( user.name ); // John
alert( user.age ); // 30
JSON hakkında daha derin bilgiyi JSON metodları, toJSON bölümünden öğrenebilirsiniz.
Eğer json düzgün gelmiyorsa JSON.parse hata üretir ve kod anında “ölür”.
Bunun ile yetinmeli miyiz? Elbette hayır.
Bu şekliyle eğer gelen veride bir hata varsa ziyaretçi nerede yanlış olduğunu bilemeyecektir. İnsanlar hata olduğunda herhangi bir hata mesajı almadan öylece ölen bir şeyden nefret ederler.
Bunun çözümü için try...catch kullanılabilir:
let json = "{ bad json }";
try {
let user = JSON.parse(json); // <-- when an error occurs...
alert( user.name ); // doesn't work
} catch (e) {
// ...çalışma buradan devam eder.
alert( "Kusura bakmayın, veride hata var. Talep tekrar yapacaktır" );
alert( e.name );
alert( e.message );
}
Burada catch bloğu sadece mesajı göstermek için kullanılmıştır. Fakat burada ağ talebi, kullanıcıya başka bir yöntem sunma, loglama için hata loginin tutulması gibi işlemler yapılabilir.
Kendi hatalarımızı atma
Diyelim ki json yazım olarak doğru da "name" özelliğini olması gerekirken yoksa ?
Aşağıdaki gibi:
let json = '{ "age": 30 }'; // verinin bütünlüğünde problem var.
try {
let user = JSON.parse(json); // <-- hata yok
alert( user.name ); // ama isim de yok!
} catch (e) {
alert( "çalışmaz" );
}
Burada JSON.parse doğru bir şekilde çalışır, "name"'in olmaması aslında bir sorundur.
Hata idaresini birleştirmek adına burada throw operatörü kullanılacaktır.
“Throw” operatörü
throw operatörü hata oluşturur.
Yazımı şu şekildedir:
throw <error object>
Teknik olarak her şeyi hata objesi olarak kullanmak mümkündür. Hatta bu ilkel tipler olan sayı, karakter dizisi gibi yapılar da olabilir. Fakat obje kullanmak, daha sı name ve message özelliklerine sahip obje kullanmak daha iyidir. ( Böylece gömülü gelen hatalar ile uyumlu olacaktır.)
JavaScript birçok standart hataya sahiptir:Error, SyntaxError, ReferenceError, TypeError vs. Bunları kullanarak da hata objesi yaratmak mümkündür.
Yazımı:
let error = new Error(message);
// or
let error = new SyntaxError(message);
let error = new ReferenceError(message);
// ...
Gömülü hatalar (objeler değil sadece hatalar) name özelliği yapıcının aynı isme sahip özelliğinde meydana gelir. message ise argümandan alınır.
Örneğin:
let error = new Error("Bir şeyler oldu o_O");
alert(error.name); // Error
alert(error.message); // Bir şeyler oldu o_O
JSON.parse ne tarz hatalar üretti bakalım:
try {
JSON.parse("{ bad json o_O }");
} catch(e) {
alert(e.name); // SyntaxError
alert(e.message); // Unexpected token o in JSON at position 0
}
Gördüğünüz gibi bu SyntaxError yani yazım yanlışıdır.
Bizim durumumuzda ise name’in olmaması yazım hatası olarak tanımlanabilir.
Bunu isimsiz öğretmen olmayacağından yazım hatası olarak tanımlayabilir.
Atacak olursak:
let json = '{ "yaş": 30 }'; // incomplete data
try {
let user = JSON.parse(json); // <-- hata yok
if (!user.name) {
throw new SyntaxError("Tanımlanmamış veri:isim yok"); // (*)
}
alert( user.name );
} catch(e) {
alert( "JSON Error: " + e.message ); // JSON Error: Tanımlanmamış veri:isim yok
}
(*) satırında throw operatörü verilen message ile bir SyntaxError hatası verir. Bu JavaScript’in hata oluşturmasına benzemektedir. try’ın çalışması akışta anında durur ve catch bölümüne atlar.
Artık catch tüm hata idaresinin yapılacağı yerdir: Buna JSON.parse ve diğer durumlar dahildir.
Tekrar atma (Rethrowing)
Yukarıdaki örnekte yanlış veri ile başa çıkmak için try...catch kullandık. Peki başka beklenmeyen hata varsa ne yapacağız? Mesela değişken tanımsız olabilir veya bilmediğimiz bir hata ile de karşılaşabiliriz.
Şu şekilde:
let json = '{ "age": 30 }'; // tamamlanmamış veri
try {
user = JSON.parse(json); // <-- user'dan önce "let" kullanmayı unuttuysak
// ...
} catch (err) {
alert("JSON Error: " + err); // JSON Error: ReferenceError: user is not defined
// (hata aslında JSON ile alakalı değil)
}
Tabii ki her şey mümkün! Programcılar da hata yapar. Yıllardır milyonlarca kişinin kullandığı open-source projelerde bile hata vardır. Hatta öyle hatalar vardır ki bulunduğunda çok büyük belaya neden olabilir (ssh’ta bulunan hata).
Biz denemelerimizde try...catchi "doğru olmayan veri"yi yakalamak için kullandık. Fakat aslında catch try’da olabilecek tüm hataları alır. Yukarıdaki örnekte beklenmeyen bir hata alır ancak yine de`“JSON Error” mesajı verir. Bu aslında kod ayıklamayı zorlaştıran bir şeydir ve yanlış kullanımdır.
Yine de ne hatası olduğunu name’den çıkarmak mümkündür.
try {
user = { /*...*/ };
} catch(e) {
alert(e.name); // "ReferenceError" tanımsız değişkene erişim hatası
}
Kural basit:
Catch sadece bildiği hataları işlemeli diğerlerini ise tekrar hata olarak atmalı.
“tekrar atma” tekniği şu şekilde detaylandırılabilir:
- Catch tüm mesajları alır.
catch(err){...}bloğunda tüm error objesi analiz edilir.- Eğer beklemediğimiz bir hata ise bu
throw errile tekrar atılır.
Aşağıdaki kodda catch sadece SyntaxError’ü idare etmektedir:
let json = '{ "age": 30 }'; // tamamlanmamış veri
try {
let user = JSON.parse(json);
if (!user.name) {
throw new SyntaxError("tamamlanmamış veri: isim yok");
}
blabla(); // beklenmeyen hata
alert( user.name );
} catch(e) {
if (e.name == "SyntaxError") {
alert( "JSON Hatası: " + e.message );
} else {
throw e; // tekrar at (*)
}
}
try...catch içerisinde eğer (*) hata tekrar atılırsa bu, try...catch in dışına taşar. Bunun daha üstte bulunan başka bir try...catch tarafından yakalanması gerekmektedir. Böyle bir ihtimal yoksa kod burada sona ermelidir.
Böylece catch bloğu aslında sadece bildiği hataları idare eder ve diğerlerini hiç kontrol etmeden paslar diyebiliriz.
Aşağıdaki örnekte bu hatalar nasıl bir try...catch seviyesi daha eklenerek idare edilebilir bunu göreceğiz:
function readData() {
let json = '{ "age": 30 }';
try {
// ...
blabla(); // error!
} catch (e) {
// ...
if (e.name != 'SyntaxError') {
throw e; // tekrar at! Nasıl idare edileceğini bilmiyor.
}
}
}
try {
readData();
} catch (e) {
alert( "External catch got: " + e ); // burada yakala!
}
Burada readData sadece SyntaxError ile nasıl başa çıkacağını biliyor. Bunun yanında dıştaki try...catch ise geri kalan her şeyi idare ediyor.
try…catch…finally
Aslında tamamı bu kadar değil!
try...catch bloğu son olarak finally ile bitebilir.
Eğer varsa aşağıdaki durumların hepsi için çalışır:
trysonrası bir hata yoksa.catchsonrası bir hata yoksa.
Yazımı şu şekildedir:
try {
... try to execute the code ...
} catch(e) {
... handle errors ...
} finally {
... execute always ...
}
Aşağıdaki kodu çalıştırmayı deneyiniz:
try {
alert("try");
if (confirm("Make an error?")) BAD_CODE();
} catch (e) {
alert("catch");
} finally {
alert("finally");
}
Kod iki türlü çalışabilir:
- Eğer “Make an error?”'a “Yes” cevabını verirseniz,
try -> catch -> finallyşeklinde sona erer. - Eğer “No” derseniz
try-> finallyşeklinde sona erer.
finally genelde try...catch’den önce bir şey yapıp bunu sona erdirmek (finally) istediğiniz durumlarda kullanılır.
Örneğin Fibonacci sayılarını hesaplayan bir fonksiyonun ne kadar sürdüğünü ölçmek istediğinizde, doğal olarak işlem başlamadan süre başlar ve işlem bittikten sonra süre biter. Fakat diyelim ki fonksiyonda bir hata var. Aşağıda uygulaması görünen fib(n)'e negatif bir sayı gönderdiğinizde veya integer olmayan bir sayı gönderdiğinizde hata döner.
finally ne olursa olsun süre ölçmeyi sonlandırmak için harika bir yerdir.
Aşağıda finally düzgün veya yanlış çalışan fib fonksiyonunun ne kadar sürdüğünü doğru olarak hesaplamamızı sağlar.
let num = +prompt("Enter a positive integer number?", 35)
let diff, result;
function fib(n) {
if (n < 0 || Math.trunc(n) != n) {
throw new Error("Must not be negative, and also an integer.");
}
return n <= 1 ? n : fib(n - 1) + fib(n - 2);
}
let start = Date.now();
try {
result = fib(num);
} catch (e) {
result = 0;
} finally {
diff = Date.now() - start;
}
alert(result || "error occured");
alert( `execution took ${diff}ms` );
Kodu çalıştırdığınızda 35 değeri girerseniz normal olarak try sonrasında finally sırası ile çalışır. Sonrasında -1 ile deneyin, anında hata alacaksınız. Çalışma süresi 0ms gösterecek. İki çalışmada da süre doğru bir şekilde tutuldu.
Diğer bir deyişle, fonksiyondan çıkmanın iki yolu verdir. Bunlar return veya throw olabilir. finally ise bunların ikisini de idare edebilir.
try...catch...finally içerisinde yereldirDikkat ederseniz result ve diff değişkenleri try...catch’den önce tanımlanmışlardır.
Diğer türlü let {...} bloğunun içerisinde olsaydı, sadece parantez içerisinde görünür olurdu.
finally ve returnFinally kelimesi try...catch’den her türlü çıkış ile çalışır. Bu doğrudan return için de geçerlidir.
Aşağıdaki örnekte try içerisinde return bulunmaktadır. Bu durumda finally sonuç dış koda iletilmeden önce çalışır.
function func() {
try {
return 1;
} catch (e) {
/* ... */
} finally {
alert( 'finally' );
}
}
alert( func() ); // önce finally içerisindeki alert çalışır sonra bu.
````smart header="`try...finally`"
`catch` olmadan hazırlanan `try...finally` yapısı da kullanışlıdır. Bunu genelde hatayı o anda idare etmek istemediğimizde kullanırız, bununla birlikte başladığımız işlemin bittiğini de garanti altına almak isteriz.
```js
function func() {
// tamamlanması gereken bir işlemi başlat. ( süre ölçme gibi )
try {
// ...
} finally {
// ne olursa olsun bitir.
}
}
```
Yukarıdaki kodda `try` içerisinde olacak herhangi bir hata doğrudan dışarı çıkacaktır. Akış dışarı sıçramadan önce `finally` çalışır.
Genel Hataları Yakalama
Aşağıdaki bölüm aslında JavaScript çekirdeğinde bulunmamaktadır.
Diyelim ki try...catch’in dışında bir hata ile karşılaştınız ve kodunuz sona erdi. Bu programlama hatası veya başka bir hata olabilir.
Böyle bir durumda ne yapmak lazım? Hataları loglayabilir, kullanıcıya bir hata gösterebiliriz.
Aslında şartnamede bunun ile ilgili bir belirti bulunmasa da çoğu ortam bunu temin eder. Örneğin Node.JS bunun için process.on(‘uncaughtException’)‘i kullanır. Tarayıcıda window.onerror’ özelliğine bir fonksiyon tanımlanabilir. Bu yakalanmayan bir hata olduğunda çalışacaktır.
Yazımı:
window.onerror = function (message, url, line, col, error) {
// ...
};
message- Hata Mesajı
url- Hatanın hangi URL’de meydana geldiği.
line,col- Hangi satır ve sütunda hatanın meydana geldiği.
error- Hata objesi.
Örneğin:
<script>
window.onerror = function(message, url, line, col, error) {
alert(`${message}\n At ${line}:${col} of ${url}`);
};
function readData() {
badFunc(); // hata meydana geldi!
}
readData();
</script>
window.onerror genel hata işleyicisinin görevi aslında kodu kurtarmak değildir. Bu anda kodu kurtarmak imkansızdır, bunun yerine geliştiriciye mesaj gönderebilir.
Bu hataları izlemek için aslında bazı servisler mevcuttur. Bunlardan bazıları https://errorception.com, http://www.muscula.com’dır.
Aşağıdaki gibi çalışırlar:
- Servise kayıt olunur ve yazdığımız koda yerleştirmek için bir kod parçası alınır.
- Bu JS içerisinde bir çeşit
window.onerroruygulaması mevcuttur. - Hata meydana geldiğinde, bu servise ağ üzerinden bir istekte bulunur.
- Servise tekrar giriş yaptığınızda arayüzde bu hataları görürsünüz.
Özet
try...catch yapısı çalışma zamanlı hataları idare eder. Tam olarak kodu çalıştırmaya çalışır ve hataları yakalar.
Yazımı:
try {
// bu kodu çalıştır
} catch (err) {
// eğer hata varsa, buraya atla
// err hata objesi
} finally {
// try/catch'den sonra her halükarda burayı çalıştır.
}
catch bölümü veya finally bölümü olmadan da çalışır. try...catch, try...finally’de doğru kullanımdır.
Hata objeleri şu özellikleri taşır:
message– insan tarafından okunabilir hata mesajıname– hatanın ismistack( standart değil ) – hatanın oluştuğu andaki yığın. Hatanın nedenini bulmak için yararlı bir özellik.
throw kullanarak biz de kendi hatalarımızı oluşturabiliriz. Teknik olarak, throw’un argümanları her şey olabilir. Fakat genelde Error sınıfından türemesi ve özelliklerini alması iyi bir yoldur. Bunları nasıl genişleteceğinizi bir sonraki bölümde görebilirsiniz.
Tekrar atma hata idaresi için temel bir desendir: bir catch bloğu her zaman hangi hataların geleceğini ve buna göre ne yapması gerektiğini bilmeli, eğer bilmiyorsa bu hatayı tekrar atmalıdır.
try...catch olmasa bile çoğu ortam “genel” bir hata idarecisi oluşturmamızı sağlar. Böylece gözden kaçan hatalar burada yakalanabilir. Tarayıcı için bu window.onerror’dur.
Yorumlar
<code>kullanınız, birkaç satır eklemek için ise<pre>kullanın. Eğer 10 satırdan fazla kod ekleyecekseniz plnkr kullanabilirsiniz)