Menu

try/catch ב-JavaScript: טיפול בשגיאות בלי קריסות

איך try/catch/finally עובד ב-JavaScript: תפיסת שגיאות, אובייקט השגיאה, זריקה מחדש, ומתי try/catch הוא הכלי הלא נכון.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

try/catch הוא רשת ביטחון, לא חגורת בטיחות

כששורה של JavaScript זורקת שגיאה, הביצוע נעצר במקום והשגיאה מתפשטת למעלה במחסנית הקריאות. אם שום דבר לא תופס אותה, התוכנית קורסת (ב-Node) או מדפיסה קיר אדום בקונסול (בדפדפנים). try/catch הוא הדרך ליירט את זה, כלומר לומר "אני יודע שזה עלול להיכשל, הנה מה שאני רוצה לעשות במקום".

הצורה הבסיסית:

JSON.parse זורק SyntaxError. הביצוע קופץ מיד לבלוק ה-catch, כשהשגיאה קשורה ל-err. ה-console.log השלישי עדיין רץ: הקריסה נבלמה.

אם בלוק ה-try מצליח בלי לזרוק, מדלגים לגמרי על בלוק ה-catch. הוא שם בשביל מסלול הכישלון.

אובייקט השגיאה

כל מה שנזרק נקשר לשם הפרמטר ב-catch (...). בדרך כלל זה מופע של Error עם שלושה שדות שימושיים:

name אומר לכם מהי תת-המחלקה (TypeError, RangeError, SyntaxError וכו', עוד עליהן במסמך הבא). message הוא התיאור הקריא לבני אדם. stack הוא העקבות המלאים, ששווים זהב בזמן דיבוג.

מלכודת אחת: JavaScript מאפשרת לזרוק כל דבר עם throw, לא רק אובייקטים של Error. קוד ישן עושה לפעמים throw "something broke". כשאתם כותבים throw משלכם, זרקו תמיד Error כדי שהקוד שקורא לכם יקבל stack trace:

finally רץ בכל מקרה

finally הוא בלוק שלישי אופציונלי שרץ בין אם נזרקה שגיאה ובין אם לא, ובין אם ה-catch טיפל בה ובין אם לא. הוא נועד לניקוי: סגירת קבצים, שחרור נעילות, הסתרת אנימציות טעינה:

ה-spinner מוסתר בין אם הטעינה הצליחה ובין אם נכשלה. בלי finally, הייתם צריכים לכתוב את השורה הזו בשני הענפים, ולשכוח אותה באחד מהם.

finally רץ אפילו אם בלוק ה-try או ה-catch מכיל return. הפונקציה מחזירה ערך אחרי ש-finally רץ. לפעמים זה מפתיע; ברוב המקרים זה בדיוק מה שרוצים.

לא תמיד צריך catch

catch הוא אופציונלי. try/finally לבד הוא חוקי ושימושי כשרוצים ניקוי מובטח אבל אין שום כוונה לטפל בשגיאה: רוצים שהיא תתפשט הלאה:

ה-try/finally הפנימי משחרר את הנעילה גם כש-fn() זורק, אבל לא בולע את השגיאה: הקוד הקורא עדיין רואה אותה. בליעת שגיאות בשקט ("זה נכשל ולא סיפרתי לאף אחד") היא אחד מסיוטי הדיבוג הנפוצים ביותר.

זריקה מחדש: לטפל בחלק, להעביר את השאר הלאה

בלוק catch לא חייב לטפל בהכול. אפשר לבחון את השגיאה, לטפל במה שהגיוני ולזרוק מחדש את השאר:

בדיקת ה-instanceof היא הדפוס: לזהות את השגיאות שאתם יודעים להתאושש מהן, ולתת לכל השאר להמשיך במעלה המחסנית. בליעת כל שגיאה עם בלוק catch ריק היא סימן לקוד בעייתי: מאבדים כל אות כשמשהו לא צפוי משתבש.

try/catch עם async/await

בתוך פונקציית async, promises שעושים להם await ונדחים הופכים לשגיאות שנזרקות, ו-try/catch מטפל בהן בדיוק כמו בשגיאות סינכרוניות:

דקות אחת: חייבים לעשות await ל-promise בתוך בלוק ה-try. אם מחזירים promise בלי await, הדחייה קורית אחרי שהפונקציה כבר יצאה, וה-catch אף פעם לא רואה אותה:

async function bad() {
  try {
    return fetch("/broken");  // no await - caller sees the rejection
  } catch (err) {
    // never runs
  }
}

כלל אצבע: בפונקציות async, עשו await לדבר שאתם רוצים ש-try/catch יכסה.

try/catch מקונן

אפשר לקנן בלוקים של try/catch כשהקוד הפנימי והחיצוני נכשלים מסיבות שונות שרוצים לטפל בהן אחרת:

ה-catch הפנימי מטפל ב"מבנה הנתונים שגוי" על ידי החזרת ברירת מחדל בטוחה. החיצוני מטפל ב"הקלט בכלל לא היה JSON" על ידי עטיפה וזריקה מחדש. קינון זה בסדר כשלכל שכבה יש אסטרטגיית התאוששות נפרדת. אם שני הבלוקים היו עושים את אותו הדבר, שטחו אותם.

מתי לא להשתמש ב-try/catch

try/catch הוא כלי לכישלונות צפויים שאפשר להתאושש מהם. הוא לא דרך לטייח באגים.

  • אל תעטפו גוף שלם של פונקציה "ליתר ביטחון". אם אין לכם תוכנית אמיתית לשגיאה, תנו לה לבעבע למעלה: שגיאה שלא נתפסה עם stack trace שימושית יותר משגיאה שקטה.
  • אל תשתמשו בו לבקרת זרימה. לבלוקים של try יש תקורה אמיתית והם מערפלים את הקוד לעומת בדיקת if. if (user) עדיף על try { user.name } catch {}.
  • אל תתפסו, תרשמו ללוג ותתעלמו. לכל הפחות, זרקו מחדש או החזירו ערך מיוחד שהקוד הקורא יכול לזהות.

המבחן המחשבתי: "מה עושה מי שמשתמש בקוד הזה כשהוא נכשל?" אם אין לכם תשובה, אתם עוד לא מוכנים לתפוס את השגיאה עם catch.

טבלת עזר מהירה

  • try { ... } catch (err) { ... }: יירוט שגיאות שנזרקות.
  • finally { ... }: תמיד רץ; השתמשו בו לניקוי.
  • throw new Error("..."): זרקו תמיד תת-מחלקות של Error כדי ש-stack traces יעבדו.
  • throw err; בתוך catch: זריקה מחדש כשאי אפשר לטפל בשגיאה.
  • await בתוך try: הכרחי כדי ש-try/catch יראה דחיות אסינכרוניות.

הבא בתור: סוגי שגיאות

TypeError, RangeError, SyntaxError: ל-JavaScript יש משפחה של מחלקות שגיאה מובנות, והיכרות עם המשמעות של כל אחת הופכת את התפיסה והדיווח למדויקים הרבה יותר. זה המסמך הבא.

שאלות נפוצות

איך try/catch עובד ב-JavaScript?

שימו קוד מסוכן בתוך try { ... }. אם משהו בפנים זורק שגיאה, הביצוע קופץ ישר לבלוק catch (err) { ... }, כשהערך שנזרק קשור ל-err. אם שום דבר לא נזרק, מדלגים על בלוק ה-catch. בלוק finally { ... } אופציונלי רץ בכל מקרה, ושימושי לניקוי.

מתי כדאי להשתמש ב-try/catch ב-JavaScript?

השתמשו בו סביב פעולות שבאמת עלולות להיכשל בזמן ריצה: JSON.parse על קלט לא מהימן, תגובות של fetch, קלט/פלט של קבצים או רשת. אל תעטפו כל שורה: אם אין לכם תוכנית להתאושש, תנו לשגיאה לבעבע למעלה. try/catch רחב סביב קוד שעובד מסתיר באגים במקום לטפל בהם.

האם try/catch תופס שגיאות אסינכרוניות?

רק כשעושים await ל-promise בתוך בלוק ה-try. קריאה חשופה somePromise() לא תיתפס: השגיאה הופכת ל-unhandled rejection. עם async/await, try/catch עובד בדיוק כמו עם קוד סינכרוני. עבור promises רגילים, השתמשו במקום זאת ב-.catch() על השרשרת.

איך זורקים שגיאה מחדש ב-JavaScript?

בתוך catch, פשוט כתבו throw err; (או זרקו שגיאה חדשה שעוטפת אותה). זה שימושי כשרוצים לטפל בחלק מהשגיאות ולתת לאחרות להמשיך הלאה: בדקו קודם את הסוג או ההודעה של השגיאה, טפלו במה שאפשר, וזרקו מחדש את השאר כדי שהקוד שקרא לכם עדיין יראה אותן.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל