Menu

Typy błędów w JavaScript: SyntaxError, TypeError, ReferenceError

Wbudowane typy błędów w JavaScript: co oznacza każdy z nich, kiedy go zobaczysz i jak czytać komunikat bez zgadywania.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

Błędy to obiekty z typem

Gdy JavaScript rzuca błąd, nie podaje ci po prostu stringa, tylko obiekt. Ten obiekt ma typ (swój konstruktor) i kilka standardowych właściwości: name, message i stack.

err.name to krótka etykieta, np. "TypeError". err.message to czytelny dla człowieka opis. err instanceof TypeError mówi ci, o jaką konkretną klasę chodzi. Znajomość typu ma znaczenie: mówi, czy problemem jest literówka, zła wartość, czy kod, który nawet się nie sparsował.

Wbudowanych typów błędów jest siedem. Trzy zobaczysz bez przerwy, cztery spotkasz od czasu do czasu.

SyntaxError: kod się nie sparsował

SyntaxError oznacza, że JavaScript nie był w stanie nawet przeczytać twojego kodu. To właściwie nie jest błąd czasu wykonania: silnik zawodzi podczas parsowania, zanim wykona się choć jedna linia. Nie da się złapać SyntaxError przez try/catch w tym samym pliku, bo cały plik zostaje odrzucony.

function greet(name {
    return "hi, " + name;
}
// SyntaxError: Unexpected token '{'

Brakujący nawias, zbędny przecinek, return poza funkcją: wszystko, co łamie gramatykę, rzuca ten błąd. Poprawka zawsze polega na naprawieniu kodu źródłowego. Jedyne miejsce, w którym możesz złapać SyntaxError, to parsowanie w czasie wykonania, np. przez JSON.parse:

JSON.parse dostaje string w czasie wykonania, więc jego błędy składni da się złapać. Błędów we własnych plikach źródłowych już nie.

ReferenceError: ta nazwa nie istnieje

ReferenceError pojawia się, gdy odwołujesz się do zmiennej, która nie została zadeklarowana w żadnym zakresie widocznym dla bieżącego kodu.

W dziewięćdziesięciu procentach przypadków to literówka (totl zamiast total). Pozostałe dziesięć procent to zakres: próbujesz użyć czegoś zadeklarowanego w innej funkcji albo module.

Jest też jedna subtelniejsza przyczyna: temporal dead zone. Deklaracje let i const istnieją od początku swojego bloku, ale nie możesz się do nich odwołać przed linią, która je deklaruje:

x jest prawdziwym wiązaniem w chwili, gdy wykonuje się console.log(x), ale nie zostało jeszcze zainicjalizowane. Stąd błąd odwołania. Poprawka to przeniesienie odwołania poniżej deklaracji.

TypeError: wartość miała zły kształt

TypeError oznacza, że wartość istnieje, ale nie jest tym, czego oczekuje operacja. Wywołanie czegoś, co nie jest funkcją, odczyt właściwości null lub undefined, przypisanie do const: to wszystko TypeError.

„Cannot read properties of null (reading 'name')” to prawdopodobnie najczęstszy komunikat błędu w całym JavaScript. Poprawka to albo zagwarantowanie, że wartość istnieje, albo zabezpieczenie odwołania przez optional chaining: user?.name.

Inne odmiany TypeError:

Wywołanie liczby, ponowne przypisanie const, wywołanie nieistniejącej metody: wartość miała zły typ do tego, o co ją poproszono.

RangeError: liczba poza zakresem

RangeError pojawia się, gdy liczba jest technicznie poprawna, ale wykracza poza dozwolony zakres danej operacji.

Klasycznym źródłem jest nieskończona rekurencja, która przepełnia stos wywołań:

„Maximum call stack size exceeded” prawie zawsze znaczy, że funkcja wywołuje samą siebie bez warunku bazowego albo że dwie funkcje wywołują się nawzajem w pętli.

URIError i EvalError: te rzadkie

URIError pochodzi z funkcji obsługujących URI (encodeURI, decodeURIComponent i spółka), gdy dostaną niepoprawne dane wejściowe:

EvalError to relikt. Nowoczesne silniki JavaScript w praktyce go nie rzucają: nadal istnieje jako konstruktor dla zgodności wstecznej. Możesz utworzyć go ręcznie, ale w prawdziwym kodzie go nie zobaczysz.

Łańcuch dziedziczenia

Wszystkie te typy dziedziczą po bazowym Error. Oznacza to, że err instanceof Error daje true dla każdego z nich, co przydaje się w ogólnym bloku catch:

Blok catch łapie wszystko, także wartości niebędące błędami, jeśli ktoś napisze throw "oops". Ta ostatnia gałąź ma znaczenie. Zawsze zawężaj przez instanceof, zanim potraktujesz złapaną wartość jak obiekt błędu.

Celowe tworzenie błędów

Gdy twój kod wykryje problem, możesz sam rzucić dowolny z wbudowanych typów. Wybierz typ, który pasuje do awarii:

Dopasowanie typu do awarii to nie tylko kosmetyka: pozwala kodowi wywołującemu pisać celowaną logikę catch zamiast parsować komunikaty błędów.

Własne klasy błędów

Gdy wbudowane typy nie pasują, rozszerz Error:

Pamiętaj o dwóch rzeczach: wywołaj super(message), żeby bazowy Error został poprawnie skonfigurowany, i ustaw this.name, żeby logi pokazywały właściwą etykietę. Własne pola, takie jak field, pozwalają kodowi wywołującemu reagować na konkretne rodzaje awarii bez parsowania stringów.

Dalej: konsola i DevTools

Znajomość typów błędów to połowa sukcesu. Druga połowa to czytanie stack trace'a i podglądanie stanu w trakcie działania programu. Narzędzia deweloperskie przeglądarki (i debugger Node) zamieniają „rzuciło błędem i nie wiem dlaczego” w kilka sekund inspekcji. O tym jest następna strona.

Najczęściej zadawane pytania

Jakie wbudowane typy błędów ma JavaScript?

JavaScript ma ich siedem: Error (typ bazowy), SyntaxError, ReferenceError, TypeError, RangeError, URIError i EvalError. Każdy to konstruktor, który tworzy obiekt błędu z właściwościami name, message i stack. Najczęściej spotkasz SyntaxError, ReferenceError i TypeError.

Czym różni się SyntaxError od TypeError?

SyntaxError oznacza, że kod nie jest poprawnym JavaScriptem: silnik nie potrafi go nawet sparsować. TypeError oznacza, że kod sparsował się poprawnie, ale w trakcie działania zrobił coś niedozwolonego, np. wywołał coś, co nie jest funkcją, albo odczytał właściwość null. Błędy składni zatrzymują cały skrypt, błędy typu pojawiają się dopiero wtedy, gdy wykona się wadliwa linia.

Kiedy w JavaScript pojawia się ReferenceError?

Gdy używasz nazwy, która nie została zadeklarowana, albo odwołujesz się do zmiennej let/const w jej temporal dead zone (zanim wykona się deklaracja). Najczęstszą przyczyną są literówki: consoel.log(x) rzuca ReferenceError: consoel is not defined. Najpierw sprawdź pisownię i zakres.

Czy mogę tworzyć własne typy błędów?

Tak. Rozszerz wbudowaną klasę Error: class ValidationError extends Error { }. Ustaw this.name w konstruktorze, żeby logi i gałęzie catch mogły go rozpoznać. Własne klasy błędów opłacają się, gdy różne awarie wymagają różnej obsługi.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ