שגיאות הן אובייקטים עם טיפוס
כש-JavaScript זורקת שגיאה, היא לא סתם מוסרת לכם מחרוזת, היא מוסרת לכם אובייקט. לאובייקט הזה יש טיפוס (ה-constructor שלו) וכמה מאפיינים סטנדרטיים: name, message ו-stack.
err.name הוא תווית קצרה כמו "TypeError". err.message הוא התיאור הקריא לבני אדם. err instanceof TypeError מגלה לכם את המחלקה המדויקת. חשוב לדעת את הטיפוס: הוא אומר לכם אם הבעיה היא שגיאת הקלדה, ערך לא תקין, או קוד שאפילו לא עבר פרסור.
יש שבעה סוגי שגיאות מובנים. שלושה תראו כל הזמן, וארבעה תפגשו מדי פעם.
SyntaxError: הקוד לא עבר פרסור
SyntaxError אומר ש-JavaScript אפילו לא הצליחה לקרוא את הקוד שלכם. זו לא באמת שגיאת זמן ריצה: המנוע נכשל בזמן הפרסור, לפני ששורה אחת רצה. אי אפשר לתפוס SyntaxError עם try/catch באותו קובץ, כי כל הקובץ נדחה.
function greet(name {
return "hi, " + name;
}
// SyntaxError: Unexpected token '{'
סוגר חסר, פסיק מיותר, return מחוץ לפונקציה: כל דבר ששובר את הדקדוק של השפה זורק את השגיאה הזו. התיקון תמיד הוא לתקן את קוד המקור. המקום היחיד שבו כן אפשר לתפוס SyntaxError הוא כשהפרסור קורה בזמן ריצה, כמו ב-JSON.parse:
JSON.parse מקבל מחרוזת בזמן ריצה, ולכן אפשר לתפוס את שגיאות התחביר שלו. שגיאות בקבצי המקור שלכם, לא.
ReferenceError: השם הזה לא קיים
ReferenceError נזרק כשמתייחסים למשתנה שלא הוצהר באף scope שהקוד הנוכחי רואה.
בתשעים אחוז מהמקרים זו שגיאת הקלדה (totl במקום total). עשרת האחוזים האחרים הם עניין של scope: אתם מנסים להשתמש במשהו שהוצהר בפונקציה או במודול אחר.
יש עוד סיבה, עדינה יותר: ה-temporal dead zone. הצהרות let ו-const קיימות מראש הבלוק שלהן, אבל אי אפשר לגשת אליהן לפני השורה שמצהירה עליהן:
x הוא binding אמיתי כבר כש-console.log(x) רץ, אבל הוא עדיין לא אותחל. מכאן שגיאת ההפניה. התיקון הוא להזיז את הגישה אל מתחת להצהרה.
TypeError: לערך הייתה הצורה הלא נכונה
TypeError אומר שהערך קיים, אבל הוא לא מהסוג שהפעולה מצפה לו. קריאה למשהו שאינו פונקציה, קריאת מאפיין של null או undefined, השמה ל-const: כולם TypeError.
"Cannot read properties of null (reading 'name')" היא כנראה הודעת השגיאה הנפוצה ביותר ב-JavaScript. התיקון הוא להבטיח שהערך קיים, או להגן על הגישה עם optional chaining: user?.name.
עוד גרסאות של TypeError:
קריאה למספר, השמה מחדש ל-const, קריאה למתודה שלא קיימת: הערך היה מהטיפוס הלא נכון למה שביקשתם ממנו לעשות.
RangeError: המספר מחוץ לטווח
RangeError נזרק כשמספר תקין מבחינה טכנית, אבל נמצא מחוץ לטווח המותר לפעולה מסוימת.
המקור הקלאסי הוא רקורסיה אינסופית, שמפוצצת את מחסנית הקריאות:
"Maximum call stack size exceeded" כמעט תמיד אומר שפונקציה קוראת לעצמה בלי מקרה בסיס, או ששתי פונקציות קוראות זו לזו בלולאה.
URIError ו-EvalError: הנדירות
URIError מגיעה מפונקציות הטיפול ב-URI (encodeURI, decodeURIComponent וחברותיהן) כשהן מקבלות קלט פגום:
EvalError הוא שריד מהעבר. מנועי JavaScript מודרניים לא זורקים אותו בפועל על שום דבר, והוא עדיין קיים כ-constructor רק לצורך תאימות לאחור. אפשר ליצור אחד ידנית, אבל לא תיתקלו בו בקוד אמיתי.
שרשרת הירושה
כל הטיפוסים האלה יורשים מ-Error בסיסי. המשמעות היא ש-err instanceof Error הוא true לכל אחד מהם, וזה שימושי ב-catch כללי:
בלוק catch תופס הכול, כולל ערכים שאינם שגיאות אם מישהו עושה throw "oops". לכן הענף האחרון חשוב. תמיד צמצמו עם instanceof לפני שאתם מתייחסים לערך שנתפס כאובייקט שגיאה.
יצירת שגיאות בכוונה
אתם יכולים לזרוק בעצמכם כל אחד מהטיפוסים המובנים כשהקוד שלכם מזהה בעיה. בחרו את הטיפוס שמתאים לכשל:
התאמת הטיפוס לכשל היא לא רק עניין קוסמטי: היא מאפשרת לקוראים לכתוב לוגיקת catch ממוקדת במקום לפרסר הודעות שגיאה.
מחלקות שגיאה מותאמות
כשהטיפוסים המובנים לא מתאימים, הרחיבו את Error:
שני דברים לזכור: קראו ל-super(message) כדי שה-Error הבסיסי יאותחל כמו שצריך, והגדירו this.name כדי שהלוגים יציגו את התווית הנכונה. שדות מותאמים כמו field מאפשרים לקוראים להגיב לסוגי כשל מסוימים בלי לפרסר מחרוזות.
הבא בתור: הקונסול וה-DevTools
להכיר את סוגי השגיאות זה חצי מהעבודה. החצי השני הוא לקרוא stack trace ולחטט במצב התוכנית בזמן שהיא רצה. ה-devtools של הדפדפן (וה-debugger של Node) הופכים את "זה זרק שגיאה ואני לא יודע למה" לכמה שניות של בדיקה. זה הנושא הבא.
שאלות נפוצות
מהם סוגי השגיאות המובנים ב-JavaScript?
ב-JavaScript יש שבעה: Error (הבסיס), SyntaxError, ReferenceError, TypeError, RangeError, URIError ו-EvalError. כל אחד מהם הוא constructor שיוצר אובייקט שגיאה עם המאפיינים name, message ו-stack. הכי הרבה תפגשו את SyntaxError, ReferenceError ו-TypeError.
מה ההבדל בין SyntaxError ל-TypeError?
SyntaxError אומר שהקוד הוא לא JavaScript תקין: המנוע אפילו לא מצליח לפרסר אותו. TypeError אומר שהקוד עבר פרסור בלי בעיה, אבל עשה משהו אסור בזמן ריצה, כמו לקרוא למשהו שאינו פונקציה או לקרוא מאפיין של null. שגיאות תחביר עוצרות את כל הסקריפט, ושגיאות טיפוס נזרקות רק כשהשורה הבעייתית רצה.
מתי מקבלים ReferenceError ב-JavaScript?
כשמשתמשים בשם שלא הוצהר, או ניגשים למשתנה let/const ב-temporal dead zone שלו (לפני שההצהרה רצה). שגיאות הקלדה הן הסיבה הנפוצה ביותר: consoel.log(x) זורק ReferenceError: consoel is not defined. בדקו קודם את האיות ואת ה-scope.
אפשר ליצור סוגי שגיאות משלי?
כן. הרחיבו את המחלקה המובנית Error: class ValidationError extends Error { }. הגדירו this.name ב-constructor כדי שלוגים וענפי catch יוכלו להבחין בה. מחלקות שגיאה מותאמות משתלמות כשכשלים שונים צריכים טיפול שונה.