Menu

TypeScript Promise: Promise<T>, Promise.all ו-allSettled

איך מגדירים טיפוסים ל-promises ב-TypeScript: הטיפוס Promise<T>, טיפוס ל-new Promise ול-resolve, איך then משנה את הטיפוס, למה catch נותן any, Promise.all עם תוצאות tuple, טיפוסי התוצאה של Promise.allSettled, ועטיפת API מבוסס callback ב-promise עם טיפוס.

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

ב-TypeScript ל-promise יש את הטיפוס Promise<T>, כש-T הוא הטיפוס של הערך שהוא מסתיים איתו. פונקציה שמחזירה promise מצהירה על כך בטיפוס ההחזרה שלה, והערך שמקבלים מ-then או מ-await הוא מטיפוס T.

המנגנון של promises (מצבים, תור ה-microtasks, שרשור) הוא JavaScript רגיל, ומוסבר ב-promises ב-JavaScript. הדף הזה עוסק בטיפוסים.

טיפוס ל-new Promise ול-resolve

כש-new Promise הוא ערך ההחזרה של פונקציה עם טיפוס החזרה מוצהר, T מגיע משם. לבדו, TypeScript לא מסיק את T מהקריאות ל-resolve, ומקבלים Promise<unknown>. העבירו את ארגומנט הטיפוס במפורש:

שני פרטים. הטיפוס של resolve נגזר מ-T, ולכן resolve("42") ב-Promise<number> היא שגיאת קומפילציה. ו-resolve() בלי ארגומנט מותר רק כש-T כולל void: ב-Promise<number> זו שגיאה TS2794 (Expected 1 arguments, but got 0. Did you forget to include 'void' in your type argument to 'Promise'?).

לצד של reject אין טיפוס. אין Promise<T, E>: promise יכול להידחות עם כל ערך.

then, catch ו-finally

כל then מחזיר promise חדש שהטיפוס שלו נקבע לפי מה שה-callback שלו מחזיר. אם ה-callback מחזיר promise, TypeScript פורק אותו, כך שאף פעם לא מקבלים Promise<Promise<T>>.

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

Promise.all מחזיר tuple

כשמקבל ליטרל מערך של promises מטיפוסים שונים, Promise.all מחזיר promise של tuple, וכל מיקום שומר על הטיפוס שלו:

אפשר לערבב גם ערכים שאינם promises, והם עוברים בלי שינוי. Promise.all נדחה ברגע שקלט אחד נדחה, ושאר התוצאות אובדות. Promise.race מסתיים בהצלחה או נדחה לפי הראשון שמסתיים, והטיפוס שלו הוא האיחוד של הקלטים; Promise.any מסתיים עם ההצלחה הראשונה ונדחה עם AggregateError רק אם כל הקלטים נכשלים.

Promise.allSettled וטיפוס התוצאה שלו

Promise.allSettled מחכה לכל הקלטים ואף פעם לא נדחה. כל תוצאה היא PromiseSettledResult<T>, איחוד שמצמצמים בעזרת השדה status:

r.value קיים רק אחרי בדיקת status, כי לגרסה הנדחית אין value. reason הוא any, מאותה סיבה כמו ב-catch. ה-filter משתמש ב-type predicate כדי שהמערך המסונן יקבל את הטיפוס של תוצאות מוצלחות.

עטיפת API מבוסס callback ב-promise

ממשקי API ישנים מדווחים על תוצאות דרך callback, לרוב בסגנון Node (err, result) => void. עטפו אותם פעם אחת בפונקציה שמחזירה promise עם טיפוס, ושאר הקוד יוכל להשתמש ב-await:

ב-Node, util.promisify עושה את זה לפונקציות שעוקבות אחרי המוסכמה (err, result), ולמודולים מובנים רבים כבר יש גרסאות promise (node:fs/promises, node:timers/promises).

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

  • החזרת T במקום שבו החתימה אומרת Promise<T>, או להפך. פונקציה שאינה async שהוצהרה כ-Promise<User> חייבת להחזיר promise; פונקציית async מחזירה אחד אוטומטית.
  • שכחה לטפל ב-promise. קריאה כמו save(user); בלי await, then או catch עוברת קומפילציה, ודחייה הופכת ל-unhandled rejection (שמסיימת תהליך Node כברירת מחדל). כלל ה-typescript-eslint no-floating-promises תופס את אלה.
  • הסתמכות על הטיפוס של .catch((e) => ...). ה-e שלו הוא any. תנו לו את הטיפוס unknown.
  • שימוש ב-new Promise סביב משהו שכבר מחזיר promise. פשוט החזירו את ה-promise הקיים או עשו לו await.

שאלות נפוצות

מה זה Promise<T> ב-TypeScript?

Promise<T> הוא הטיפוס של promise שמסתיים בהצלחה עם ערך מטיפוס T. פונקציה שמחזירה Promise<string> מחזירה promise שה-callback של then שלו, או await, נותן string. promise שמסתיים בלי ערך הוא Promise<void>.

איך נותנים טיפוס ל-new Promise ב-TypeScript?

מעבירים את ארגומנט הטיפוס: new Promise<number>((resolve, reject) => ...). בלעדיו, TypeScript לא יכול להסיק את הערך מהקריאות ל-resolve, והתוצאה היא Promise<unknown>. ל-promise שמסתיים בלי כלום, השתמשו ב-new Promise<void>(resolve => ...) כדי ש-resolve() בלי ארגומנט יהיה מותר.

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

any. הפרמטר reason של ה-callback של catch מוגדר כ-any בספרייה הסטנדרטית, כי אפשר לזרוק או לדחות כל דבר. תנו לו בעצמכם את הטיפוס unknown (.catch((e: unknown) => ...)) וצמצמו עם instanceof Error לפני השימוש.

איך Promise.all עובד עם טיפוסים ב-TypeScript?

Promise.all על ליטרל מערך מחזיר טיפוס tuple עם איבר אחד לכל קלט, לפי הסדר: Promise.all([getUser(), getCount()]) הוא Promise<[User, number]>, ולכן destructuring נותן לכל ערך את הטיפוס שלו. על מערך מאותו טיפוס, T[], הוא מחזיר Promise<T[]>.

מה ההבדל בין Promise.all ל-Promise.allSettled?

Promise.all נדחה ברגע שאחד הקלטים נדחה. Promise.allSettled תמיד מסתיים בהצלחה, עם מערך של אובייקטי PromiseSettledResult<T>: { status: "fulfilled", value } או { status: "rejected", reason }. בדקו את status כדי לצמצם כל אחד מהם.

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

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

להתחיל