Menu

Try Catch ב-TypeScript: טיפוסי שגיאות ושגיאות מותאמות

טיפול בשגיאות ב-TypeScript: למה המשתנה של catch הוא unknown, איך מצמצמים אותו עם instanceof Error, זריקת שגיאות, כתיבת מחלקות שגיאה מותאמות עם name ו-cause, והדפוס של טיפוס Result לשגיאות צפויות.

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

TypeScript משתמשת ב-try, catch, finally ו-throw של JavaScript. החלק שייחודי ל-TypeScript הוא המשתנה של catch: כש-strict פעיל, הטיפוס שלו הוא unknown, ולכן בודקים מה נזרק לפני שמשתמשים בו.

הצד של זמן הריצה (איך ה-stack נפרם, אילו טיפוסי שגיאה מובנים קיימים) מוסבר ב-try/catch ב-JavaScript.

למה המשתנה של catch הוא unknown

JavaScript יכולה לזרוק כל דבר: Error, מחרוזת, מספר, undefined, אובייקט מספרייה. TypeScript לא יכולה לדעת מה, ולכן תחת strict (הדגל useUnknownInCatchVariables) המשתנה הוא unknown וחייבים לצמצם אותו לפני שימוש:

index.ts(5,34): error TS18046: 'e' is of type 'unknown'.

בלי strict, e הוא any ואותו קוד עובר קומפילציה, ואז משתבש בזמן ריצה כשנזרקת מחרוזת: e.message הוא undefined, וכל דבר עמוק יותר, כמו e.message.length, זורק TypeError. גם הערת טיפוס לא תעזור: catch (e: Error) היא השגיאה TS1196 (Catch clause variable type annotation must be 'any' or 'unknown' if specified).

צמצום השגיאה

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

catch שמטפל רק בחלק מהשגיאות צריך לזרוק מחדש את השאר: if (!(e instanceof ValidationError)) throw e;. בליעה של שגיאות לא ידועות מסתירה באגים אמיתיים.

זריקת שגיאות

throw מקבל כל ביטוי, ו-TypeScript לא מגבילה אותו. בכל זאת, זרקו אובייקטי Error: יש להם stack trace, וכל בדיקת instanceof Error בבסיס הקוד תלויה בזה.

לפונקציה שתמיד זורקת יש את טיפוס ההחזרה never, ו-TypeScript משתמשת בו לצמצום אחרי הקריאה:

מאז ES2022, Error מקבל ארגומנט שני עם cause, שמשרשר שגיאה ברמה נמוכה לשגיאה שאתם זורקים: throw new Error("could not load settings", { cause: e }). הקורא יכול לקרוא את err.cause (מטיפוס unknown).

מחלקות שגיאה מותאמות

תת-מחלקה של Error מאפשרת לקוראים להבחין בין כישלונות עם instanceof ולשאת מידע נוסף. קבעו את name, כי זה שנורש הוא "Error" והוא מופיע בלוגים וב-String(err):

בדקו קודם את המחלקה הספציפית ביותר, כי NotFoundError הוא גם HttpError. המגדיר override אופציונלי כאן, אלא אם noImplicitOverride פעיל.

מדריכים ישנים מוסיפים Object.setPrototypeOf(this, new.target.prototype) לכל בנאי של שגיאה. זה היה נחוץ כשקימפלו מחלקות לפונקציות ES5, שבהן instanceof נשבר לתתי-מחלקות של Error. TypeScript 7 כבר לא תומכת ב-target: "es5" (היא מדווחת TS5108: Option 'target=ES5' has been removed), ועם ES2015 ומעלה class ... extends Error המקורי עובד, כמו למעלה.

הדפוס Result

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

קריאה של r.value לפני בדיקה של r.ok היא שגיאת קומפילציה, וזו בדיוק המטרה. השתמשו בחריגות לדברים לא צפויים (באגים, חיבור שנפל) ובערכי Result לתוצאות שהקורא חייב להחליט לגביהן.

שגיאות בקוד אסינכרוני

promise שנדחה הופך לשגיאה שנזרקת ב-await, ולכן try/catch סביב await עובד באותה צורה, והמשתנה שוב unknown. ההבדל היחיד הוא .catch() על promise: לפרמטר של ה-callback שלו יש את הטיפוס any, לא unknown, אז תנו לו הערת טיפוס בעצמכם (.catch((e: unknown) => ...)). ראו async/await לדוגמאות.

טעויות נפוצות

  • קריאה של e.message בלי צמצום. תחת strict זה לא עובר קומפילציה; בלי strict זה נשבר כשנזרק משהו שאינו Error.
  • לתפוס הכול ולהמשיך. טפלו בשגיאות שאתם מצפים להן וזרקו מחדש את השאר.
  • לשכוח את name בשגיאות מותאמות. אז בלוגים כתוב Error על כולן.
  • זריקת מחרוזות. ל-throw "failed" אין stack trace, והוא נכשל בבדיקות instanceof Error.

שאלות נפוצות

מה הטיפוס של השגיאה בבלוק catch ב-TypeScript?

unknown כש-strict פעיל (הדגל useUnknownInCatchVariables), ואחרת any. JavaScript יכולה לזרוק כל ערך, לא רק אובייקטי Error, ולכן TypeScript מחייבת אתכם לבדוק. צמצמו אותו עם if (e instanceof Error) לפני שקוראים את e.message.

האם אפשר לתת למשתנה של catch את הטיפוס Error ב-TypeScript?

לא. catch (e: Error) היא השגיאה TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified. רק unknown ו-any מותרים, כי שום דבר לא מבטיח מה נזרק. במקום זה, צמצמו בתוך הבלוק.

איך זורקים שגיאה ב-TypeScript?

כמו ב-JavaScript: throw new Error("message"), או מופע של תת-מחלקה מובנית או מותאמת כמו new RangeError(...). TypeScript מאפשרת לזרוק כל ערך, אבל זריקה של אובייקטי Error שומרת stack trace וגורמת לבדיקות instanceof Error לעבוד.

איך יוצרים מחלקת שגיאה מותאמת ב-TypeScript?

הרחיבו את Error, קראו ל-super(message, options), וקבעו את name: class NotFoundError extends Error { name = "NotFoundError"; }. שדות נוספים נכנסים לבנאי. עם כל target נתמך (ES2015 ומעלה), instanceof NotFoundError עובד בלי תיקון של ה-prototype.

האם יש ב-TypeScript checked exceptions או פסוקית throws?

לא. הטיפוס של פונקציה לא אומר כלום על מה שהיא עלולה לזרוק, והקומפיילר אף פעם לא בודק ששגיאות מטופלות. לשגיאות שהקורא אמור לטפל בהן, החזירו אותן כערכים עם טיפוס Result (discriminated union של הצלחה וכישלון), כדי שמערכת הטיפוסים כן תבדוק.

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

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

להתחיל