Zgodnie z tytułem tego wpisu, dziś przedstawię jak wygląda obsługa wyjątków JavaScript. Biorąc więc pod uwagę wymagania egzaminacyjne zamieszczone na stronach Microsoftu, omówimy dziś takie zagadnienia jak: ustawianie i odpowiadanie na kody błędów; rzucanie wyjątków; sposoby sprawdzania wartości null; implementacja bloku try/catch/finally.

Jak zwykle zapraszam do lektury! ;)

Sprawdzenie “null check”

W języku JavaScript, w odróżnieniu od innych języków, istnieją dwie wartości oznaczające wartość niezdefiniowaną - oprócz wartości ‘null’ mamy tutaj jeszcze wartość ‘undefined’. Między nimi istnieje zasadnicza różnica - ‘null’ jest specjalną wartością oznaczającą “wartość nieustaloną”, która de facto jest obiektem. Natomiast ‘undefined’ oznacza, że dany obiekt nie istnieje, nie został nawet nigdzie zadeklarowany. Spójrzmy na taki przykład:

// wartosc undefined - someObject nie jest nigdzie zadeklarowny
alert(someValue);

// takze undefined - someObject nadal nie ma wartosci
var someValue;
alert(someValue);

// wartosc null - do someValue jawnie przypisujemy wartosc
var someValue = null;
alert(someValue);

Myślę, że kod powyższy pokazuje różnicę - jeśli odwołujemy się do obiektu, który nie został jeszcze zadeklarowany, zwracana jest wartość ‘undefined’ (tego można się było w zasadzie spodziewać, chociaż języki silniej typowane zwróciłyby tutaj błąd). Jeśli z kolei obiekt zadeklarujemy ale nie przypiszemy mu wartości, obiekt taki również ma wartość ‘undefined’ (to z kolei może być mylące, ponieważ np. C#, w takim przypadku automatycznie ustawia takiemu obiektowi wartość ‘null’). Jak widać na końcu przykładu, obiekt ma wartość ‘null’, dopiero kiedy jawnie mu ją przypiszemy.

Porównania

O tym co napisałem powyżej należy pamiętać w kontekście sprawdzania czy obiekt istnieje przed użyciem go (ang. null check):

var x = null;
var y;

if (x == null) {
    alert('x jest rowne null');
}

if (y == null) {
    alert('y jest rowne null'); // to tez sie wykona !!
}

if (y === null) {
    alert('y jest rowne null'); // dokladne sprawdzenie - nie wykona sie
}

y = 1; // przypisanie wartosci do y!

// jednoczesne sprawdzenie na undefined i null
if (x) {
    alert('x istnieje i nie jest null'); // to sie nie wykona bo x = null
}

// jednoczesne sprawdzenie na undefined i null
if (y) {
    alert('y istnieje i nie jest null'); // to sie wykona bo y = 1
}

Powyższy przykład pokazuje różne sposoby sprawdzania czy obiekt istnieje. Na początku ustanawiamy dwie wartości: ‘x’ oraz ‘y’ - ‘x’ ma jawnie nadaną wartość ‘null’, natomiast ‘y’ nie, więc jest ‘undefined’. Następnie mamy szereg sprawdzeń.

W liniach od czwartej do szóstej, mamy zwykłe porównanie z wartością ‘null’ - w tym przypadku wszystko wygląda normalnie, ‘x’ ma wartość ‘null’ więc alert się wyświetla.

Kolejne sprawdzenie (w liniach od ósmej do dziesiątej) to już ciekawszy przypadek. Tym razem za pomocą operatora ‘==’, porównujemy ‘y’ z wartością ‘null’ - i tutaj niespodzianka - porównanie zwraca wartość ‘true’ i alert również się wyświetla, mimo że przecież ‘y’ jest ‘undefined”!!

Dopiero użycie operatora dokładnego porównania ‘===’ zwraca false (linie od dwunastej do czternastej). Należy więc zawsze pamiętać, że ‘null == undefined’ zwraca ‘true’ a ‘null === undefined’ zwraca ‘false’ podczas “null check’ów”!!!!

W dalszej części przykładu, mamy dwa kolejne sprawdzenia, które pokazują w jaki sposób można poradzić sobie ze sprawdzeniem czy zmienna ma po prostu ustawioną jakąś wartość różną zarówno od ‘null’ jak i ‘undefined’ - wystarczy w wyrażeniu ‘if’ użyć samej zmiennej bez żadnych operatorów porównania.

Blok try/catch/finally

Jeśli chodzi o obsługę wyjątków za pomocą bloku try/catch/finally, to język JavaScript nie różni się za bardzo od innych języków. Spójrzmy na przykład:

try {
    var x = someFunc(); // uzycie nie zdefiniowanej funkcji
} catch(e) {
    alert(e.message); // wyswietlamy info o bledzie
} finally {
    alert('a to sie zawsze wykona');
}

Jak widać, nie ma tutaj niczego niezwykłego, w bloku ‘try’ użyta została funkcja, która nie została wcześniej zdefiniowana. To powoduje wyjątek, który przechwytujemy w bloku ‘catch’ (do bloku tego przekazywany jest sam wyjątek, z którego można odczytać przydatne informacje - tutaj informację o wyjątku). Blok ‘finally’ wykonuje się zawsze - można go wykorzystać do “posprzątania” przed np. zakończeniem skryptu.

Oczywiście jeśli używamy konstrukcji ‘try’, jesteśmy zobowiązani użyć jej co najmniej z ‘catch’ - blok ‘finally’ jest opcjonalny. Możliwe jest również zagnieżdżanie bloków ‘try/catch/finally’ wewnątrz innych bloków tego typu.

Rzucanie wyjątków i kody błędów

Aby rzucić własny wyjątek, używa się obiektu ‘Error’. Najlepiej spojrzeć na przykład:

try {
    throw new Error('wlasny blad!');
} catch(e) {
    alert(e.message); // wyswietlamy info o bledzie
}

W powyższym przykładzie, w bloku ‘try’ rzucamy własny wyjątek poprzez użycie słowa kluczowego ‘throw’ wraz z utworzeniem nowego obiektu ‘Error’ (zupełnie jak w innych językach programowania). W konstruktorze obiektu ‘Error’ podajemy informację o błędzie. Tak utworzony obiekt błędu, jest później dostępny w bloku ‘catch’, poprzez parametr ‘e’. W przykładzie korzystamy z właściwości ‘message’ dostępnej w tym parametrze - poprzez tę właściwość mamy dostęp do informacji, którą wcześniej przekazaliśmy w konstruktorze obiektu ‘Error’.

Zamiast wprost tworzyć obiekt ‘Error’, można to zrobić również w sposób skrócony:

throw ('wlasny blad!');

Wyspecjalizowane obiekty błędów

Oprócz obiektu ‘Error’, możemy użyć sześciu bardziej wyspecjalizowanych wersji błędów. Poniżej ich lista w raz z opisem:

  • EvalError - błąd podczas wykonywania funkcji ‘eval()’
  • RangeError - wartość z poza zakresu
  • ReferenceError - użyto nielegalnej referencji
  • SyntaxError - błąd syntaktyczny wewnątrz funkcji ‘eval()’ - innych błędów syntaktycznych nie można złapać wewnątrz bloku ‘try’; jest on wyłapywany wcześniej przez przeglądarkę (coś jak błąd podczas kompilacji w innych językach)
  • TypeError - występuje gdy zmienna ma inny typ niż oczekiwany
  • URIError - błąd podczas dekodowania lub enkodowania URI

Tytuł tego paragrafu mówi też o kodach błędów - jest to temat związany właśnie z obiektem ‘Error’. Otóż konstruktor tego obiektu, może przyjmować, oprócz opisu błędu, również liczbę oznaczającą kod błędu:

try {
    throw new Error(100, 'wlasny blad!');
} catch(e) {
    // wyswietlamy info o bledzie
    alert('kod:' + e.number + ', info: ' + e.message);
}

Jak widać, kod błędu podajemy jako pierwszy parametr konstruktora. Później możemy się do tego numeru dostać poprzez właściowość ‘number’.

W przypadku standardowych błędów (takich które są rzucane automatycznie, kiedy wystąpi błąd w kodzie) kod błędu jest wartością 32-bitową, z czego pierwsze 16 bitów oznacza kod funkcji (ang. facility code), natomiast kod błędu znajduje się w pozostałych 16 bitach - w związku z tym, do odczytania kodu błędu trzeba użyć operatora ‘&’ (bitowy AND). Poniżej przykład:

try {
    var x = y; // blad - y jest nie zdefiniowane
} catch(e) {
    alert("kod bledu: " + (e.number & 0xFFFF));
}

Co kodów błędów, to należy wiedzieć, że to co napisałem dotyczy tylko Internet Explorera! Pozostałe przeglądarki albo nie pozwalają na podanie w konstruktorze niczego poza opisem błędu (Chrome, Opera, Safari) albo pozwalają na podanie większej liczby parametrów ale nie ma wśród nich kodu błędu (Firefox) - informacja zaczerpnięta stąd.

Zdarzenie onError

Jest jeszcze jeden sposób na obsługę wyjątków JavaScript. Za każdym razem gdy na stronie internetowej wystąpi błąd, wywoływane jest zdarzenie ‘error’ elementu ‘window’. Element ten posiada więc właściwość ‘onerror’, do której można przypisać funkcję obsługującą zdarzenie błędu. Spójrzmy na przykład:

// definicja funkcji obslugujacej zdarzenie error
window.onerror = function (msg, url, line) {
   alert("komunikat bledu : " + msg );
   alert("url : " + url );
   alert("numer linii : " + line );
}

var x = y + 5; // ta linia powoduje blad

W powyższym przykładzie widzimy definicję funkcji anonimowej przypisanej do właściwości ‘onerror’ obiektu ‘window’. Funkcja ta przyjmować może trzy parametry wejściowe: pierwszym jest komunikat błędu; drugim adres URL strony, na której wystąpił błąd; trzecim linia w kodzie strony. Możliwe jest również, zdefiniowanie funkcji obsługującej błąd, która nie będzie pobierała żadnych parametrów.

Po wykonaniu kodu z linii ósmej, która jest błędna, wywołane zostanie zdarzenie ‘error’ a co za tym idzie, wykonany zostanie kod opisanej wyżej funkcji, która wyświetli na ekranie informacje przekazane w parametrach wejściowych.

Nie tylko obiekt ‘window’ może wywoływać zdarzenie ‘error’. Właściwość ‘onerror’ posiada wiele elementów DOM, dzięki czemu możemy dodawać do nich funkcje, które wykonane zostanę gdy coś pójdzie nie tak (w ramach danego obiektu DOM).

Obsługa wyjątków JavaScript - podsumowanie

To w zasadzie wszystko o czym chciałem dziś napisać. Mam nadzieję, że to co napisałem w wystarczający sposób pokrywa wymagania egzaminu 70-480 oraz w pełny wyjaśnia na czym polega obsługa wyjątków JavaScript. W kolejnym odcinku zajmę się impelentacją funkcji “callback”. Jak zwykle zapraszam!