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)