פונקציית callback לא קבועה, מהי הדרך המומלצת להגדיר את ה-Type של הקולבאק (TS)
-
יש לי מחלקה בשם
Instructions
, שיש לה מתודה סטטית עם השםoperate
.
המתודה הנ"ל מקבלת שני ארגומנטים וודאים, ועוד שנים אפשריים: הראשון הוא פונקציית callback שהיא צריכה להעביר לה ארגומנטים ולקרוא לה, ושאר הארגומנטים הם אותם הארגומנטים שיועברו לפונקציית ה-callback.
פונקציית ה-callback יכולה להיות אחת מתוך שלושה סוגי פונקציות: הסוג הראשון מקבל ארגומנט אחד ומבצע עליו פעולה מסוימת, הסוג השני מקבל שני ארגומנטים, והשלישי מקבל שלושה ארגומנטים. הספק שלי הוא איך להתייחס ל-Type של ה-callback הנ"ל.בהתחלה חשבתי ליישם את זה בערך בצורה כזו:
interface Operand { // ... } interface OperationFunc { (dstOperand: Operand, srcOperand?: Operand, powerEvaluation?: number): void; } class Instructions { static operate (callback: OperationFunc, dstOperand, srcOperand?, powerEvaluation?) { callback(dstOperand, srcOperand, powerEvaluation); } }
מה שהתברר כלא אפשרי מכמה סיבות. הסיבה המרכזית היא, שפונקציית ה-callback כפי שהזכרתי היא לא אותה פונקציה שפעם מפעילים אותה עם ארגומנט אחד ופעם עם שני ארגומנטים. אלא - אלו פונקציות שונות, שכל פעם אני מעביר פונקציה אחרת כ-callback בהתאם לצורך. לפעמים מדובר בפונקציה שמקבלת ארגומנט בודד, לפעמים שני ארגומנטים ולפעמים שלושה. כמובן שכשאני קורא למתודה operate אני מעביר את שאר הארגומנטים בהתאמה ל-callback המועבר איתם.
בעקבות הבעיה הנ"ל, חשבתי להפוך את המתודה
operate
לגנרית, שמקבלת את ה-Type של ה-callback, ואת שאר הארגומנטים היא מקבלת כאובייקט נפרד (בשביל האובייקט הזה היא מקבלת Type גנרי נוסף). משהו כזה:interface Operand { // ... } interface UnaryOperation { (dstOperation: Operand): void; } interface BinaryOperation { (dstOperand: Operand, srcOperand: Operand): void; } interface SignBinaryOperation { (dstOperand: Operand, srcOperand: Operand, powerEvaluation: number): void; } interface UnaryOperationArgs { dstOperand: Operand; } interface BinaryOperationArgs extends UnaryOperationArgs { srcOperand: Operand; } interface SignBinaryOperationArgs extends BinaryOperationArgs { powerEvaluation: number; } class Instructions { static operate<OperationType = UnaryOperation | BinaryOperation | SignBinaryOperation, OperationArgs = UnaryOperationArgs | BinaryOperationArgs | SignBinaryOperationArgs> (callback: OperationType, operationArgs: OperationArgs) { callback(operationArgs); } }
הייתי צריך כמובן להמיר את כל הפונקציות שמתוכננות להישלח בתור callback, שיקבלו את הארגומנטים כאובייקט.
אבל אני עדיין לא מרוצה מכמה סיבות. הראשונה היא, שאני לא יכול להיות מרוצה כל עוד TSC לא מרוצה, ומכיוון שהוא לא יכול לדעת מראש שה-callback שיועבר לו אמור לקבל את אותו אובייקט ארגומנטים, הוא זורק שגיאה על כך.
והסיבה השנייה היא שהקוד (לטעמי לפחות) נראה זוועה ככה, וחייבת להיות דרך יותר אלגנטית לעשות דבר כזה...
תכלס השאלה שלי היא, מהי הדרך המומלצת ליישם כזאת מתודה? אודה מאוד למי שיוכל לעזור לי בעניין.
-
המתודה
Operate
איננה מקבלת כל אינדיקציה על טיב הפעולה שהיא אמורה לבצע, ניתן להסיק את אחת משתי האפשרויות:- שהיא תבדוק בעצמה (Runtime type checking) לפי מספר הפרמטרים שסופקו
- טיב הפעולה אינו רלוונטי עבורה
כדי לשפר את הType-Safety עבור הקוד שיעשה שימוש במתודה, ניתן לעשות שימוש בFunction Overloading:
class Instructions { public static operate(callback: UnaryOperation, dst: number) public static operate(callback: BinaryOperation, dst: number, src: number) public static operate(callback: SignBinaryOperation, dst: number, src: number, powerEvaluation: number) public static operate(callback: Function, dst: number, src?: number, powerEvaluation?: number) { // Note: You'll have to do type checking manually // here if you want differing behavior based on the required operation type } }
שימוש:
operate((x) => { }, 1) // Works. Operation: Unary operate((x, y, z) => { }, 1, 2) // Fails (x, y, z) => void' is not assignable to parameter of type 'BinaryOperation operate((x, y, z) => { }, 1, 2, 3) // Works. Operation: SignBinary
נ.ב. שימוש בUnion Types לא יפתור את הבעיה, משום שהסוג המדויק של
OperationType
לעולם לא יהיה ידוע בתוך המתודה בזמן כתיבת הקוד. -