‏הצגת רשומות עם תוויות TypeScript. הצג את כל הרשומות
‏הצגת רשומות עם תוויות TypeScript. הצג את כל הרשומות

להבין את package.json ואת package-lock.json



הקובץ package.json

הקובץ package.json שומר רשימה של כל הספריות שהתקנתי והגרסאות שלהם.
בכל פעם שנריץ את הפקודה:

npm install

npm ישתמש בקובץ package.json כדי לקבוע איזה ספריות ואיזה גרסאות יותקנו.

ליד כל ספריה כתוב את הגרסה שבה השתמשתי, למשל:

"axios": "^1.6.8"

הספריה axios הותקנה עם גרסה 1.6.8.

מספרי הגרסאות

כל גרסה בנויה משלוש מספרים. השמאלית נקראת major, האמצעית minor, והימנית patch.

1.6.8 [major minor patch]

  • major - בין גרסאות major יכול להיות שינוי משמעותי ולפעמים הוא לא backward compatible
  • minor - בין גרסאות minor השינוי צריך להיות backward compatible, ואמור לעבור ללא בעיות.
  • patch - בין גרסאות patch השינוי צריך להיות backward compatible, והוא בד"כ מציין תיקון באגים.

סימנים מיוחדים

  • אם מספר הגרסה מופיע בלי סימנים מיוחדים, npm יתקין בדיוק את הגרסה שציינו.
"axios": "1.6.8"
  • הסימן ^ (נקרא Caret Symbol), למשל:

"axios": "^1.6.8"

הוא מציין שכל פעם שאני עושה npm install ה-npm ינסה לשדרג לי את גרסאות minor ו-patch אבל לא major. ולכן במקרה של 1.6.8 הוא יכול להתקין לי לדוגמה את 1.6.9, 1.7.3, 1.12.15 אבל לא 2.0.0.

  • הסימן ~ (נקרא Tilde Symbol), למשל:

"axios": "~1.6.8"

הוא מציין ש-npm ינסה לשדרג לי את גרסאות patch אבל לא minor, major. לכן הוא יכול להתקין לי 1.6.9, 1.6.11 אבל לא 1.7.0.

  • הסימן < (גדול מ-), למשל:

"axios": ">1.6.8"

 מציין ש-npm יתקין רק גרסאות גדולות מ-1.6.8.

  • הסימנים > (קטן מ-), => (קטן או שווה מ-), =< (גדול או שווה מ-), כמו שהוסבר בסעיף הקודם גם כאן הם מציינים ל-npm להתקין רק גרסאות קטנות מגרסה מסוימת, קטנות או שוות, גדולות או שוות.

  • הסימן || (OR), למשל:

"axios": "<1.2.0 || >1.6.8"

מאפשר לציין כמה טווחים. במקרה שלנו הוא מציין ש-npm יתקין רק גרסאות קטנות מ-1.2.0 או גדולות מ-1.6.8.

יש עוד סימנים מיוחדים וניתן לקרוא עליהם באתר של npm כאן.

הקובץ package-lock.json

כפי שראינו, כל פעם שנריץ npm install יש אפשרות שיותקנו אצלנו גרסאות שונות של ספריות. הדבר הזה יכול לגרום לבעיות. למשל כשיש צוות של מפתחים שעובדים על פרויקט, יש סיכוי טוב שלכל אחד יותקנו גרסאות שונות של הספריות שהפרויקט תלוי בהם.

הקובץ package-lock.json בא לפתור את הבעיה הזו.

בקובץ הזה כתוב את כל המידע שנצרך כדי לדעת איזה גרסאות בדיוק הותקנו בפרוייקט. כל פעם שאני מריץ npm install, אז npm יבדוק אם יש לי קובץ package-lock.json. אם יש, הוא יתקין בדיוק את הגרסאות שכתובות בו. אם אין לי את הקובץ הזה, הוא יחפש את package.json וישתמש בו.

הקובץ package-lock.json נוצר אוטומטית ומתעדכן אוטומטית.

לכן, כדאי מאוד לשמור את הקובץ package-lock.json ב-source control שלנו כחלק מהפרוייקט. ככה כל מפתח שיצטרף לפרוייקט יתקין בדיוק את אותן ספריות ב-node_modules כמו שאנחנו משתמשים, ונעבוד בדיוק על אותו קוד.



מצד שני, ממש לא כדאי למחוק אותו, כי אז נאבד את כל המידע על הגרסאות של הספריות שאנו משתמשים בהן, וכשנעשה npm install ה-npm לא ימצא package-lock.json וישתמש ב-package.json כדי להתקין את הספריות, וכך, ברוב המקרים, חלק מהספריות יותקנו בגרסאות חדשות יותר ממה שהיה לנו עד עכשיו.

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

עדכון ידני של package.json ופקודת npm ci

אני ממליץ לא לעדכן את package.json באופן ידני. זה כמובן אפשרי, ואם יש צורך מיוחד נעשה את זה. אבל ככלל עדיף שהוא יתעדכן אוטומטית. אם למשל אני רוצה לעדכן את axios לגרסה 1.6.9 עדיף להשתמש בפקודה:

npm install axios@1.6.9

והיא גם תעדכן אוטומטית את package.json ואת package-lock.json. הדרך הזו טובה יותר מאשר לעדכן ידנית את package.json ולהריץ npm install.

הדבר נכון עוד יותר לגבי עדכון ידני של package-lock.json. גם זה אפשרי, אבל הרבה פעמים זה עלול להביא לטעויות כי הקובץ הזה הרבה יותר מורכב ויש בו כמה חלקים שדורשים עדכון, ולכן עדיף לא לשנות אותו ידנית.

נקודה חשובה שכדאי להכיר - אם יש סתירה בין package.json ל-package-lock.json, אז package.json גובר. למשל אם הגרסה שמוגדרת ב-package-lock.json לא נמצאת בטווח שמוגדר ב-package.json אז תותקן גרסה לפי מה שכתוב ב-package.json והקובץ package-lock.json יעודכן.

לדוגמה, אם ב-package.json כתוב:

"axios": "^1.6.8"

אחרי הרצת npm install מה שיהיה כתוב ב-package-lock.json זה משהו כזה:

"axios": "1.6.8"

לאחר מכן נשנה ידנית את package.json ונכתוב:

"axios": "^1.6.9"

מה שיש ב-package-lock.json כבר לא מתאים לזה, ולכן אם נריץ npm install תותקן גירסה 1.6.9 (במידה והיא קיימת) ו-package-lock.json יעודכן ל:

"axios": "1.6.9"

אגב, אם רוצים להשתמש רק במה שכתוב ב-package-lock.json בלי להתחשב ב-package.json אפשר להשתמש בפקודה:

npm ci

אבל אם יש סתירה בין package.json ל-package-lock.json כמו במקרה שלעיל, נקבל שגיאה.

הפקודה npm ci טובה עבור סביבות אוטומטיות כמו continues integration כשאתה רוצה להיות בטוח שתתקין רק את מה שכתוב ב-package-lock.json.

האם אפשר להשתמש רק ב-package.json עם גרסאות מדוייקות?

נשאלת השאלה, האם אפשר להשתמש רק ב-package.json עם גרסאות מדוייקות (בלי סימונים מיוחדים) למשל בצורה הבאה:

"axios": "1.6.8"

כדי להבטיח שכל מי שיריץ npm install יקבל את אותו עץ ספריות?

התשובה היא: לא. אמנם package.json יכול להבטיח לך שאותם גרסאות מדוייקות שכתבת אכן יותקנו לך, אבל הוא לא יכול להבטיח לך איזה גרסאות יותקנו לספריות שהספריות שלך תלויות בהן.

אם תלך לספריית node_modules ותפתח שם את אחת הספריות שהתקנת, תיראה שגם לה יש קובץ package.json שמפרט את הספריות שהיא תלויה בהן (נהוג לקרוא להן nested dependencies). ככה זה כמעט בכל ספריה שתתקין. את הגרסאות של הספריות האלו package.json לא מציין ולא מבטיח לך מה יותקן.

לעומת זאת, package-lock.json כן שומר את מספרי הגרסאות של ה-nested dependencies ואיתו אתה יכול להיות בטוח שתקבל סביבה זהה אם תריץ npm install כשיש לך package-lock.json.


אם אהבתם, תכתבו משהו למטה...

למה בכלל להשתמש ב-TypeScript





TypeScript זו שפה שפותחה על ידי מיקרוסופט והיא בעצם תת שפה ל-JavaScript. לאחרונה אנחנו רואים שהשימוש ב-TypeScript הופך להיות נפוץ יותר ונשאלת השאלה, למה בכלל להשתמש ב-TypeScript? אנחנו כבר מכירים את JavaScript אז למה ללמוד עכשיו שפה חדשה ולבזבז על זה זמן?

היתרונות של TypeScript

קוד מסודר וברור - קוד שכתוב ב-TypeScript הוא קוד מסודר וברור יותר מ-JavaScript, כיון שב-TypeScript מציינים את סוג המשתנים וכך ברור יותר מה תפקיד המשתנה. כמו כן מציינים את סוג החזרה של הפונקציות וכך ברור יותר מה תפקידה של הפונקציה. בנוסף יש תמיכה מלאה במחלקות (class) וממשקים (interface), וניתן לכתוב קוד שהוא object oriented בצורה קלאסית וברורה.
דבר נוסף שתורם לסדר זה שב-TypeScript מגדירים משתנים בעיקר בעזרת let (ולא ע"י var). השימוש ב-let יותר אינטואיטיבי מבחינת ה-scoping של המשתנה (ניתן לקרוא על let יותר לעומק פה).
ויש עוד כללים שתורמים לסדר, כמו זה שלא ניתן להשתמש במשתנה לפני שהגדרנו אותו. אלא קודם מגדירים משתנה ורק אחר כל משתמשים בו. ועוד ועוד...

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

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

בנוסף, אם משתמשים ב-editor שתומך ב-intellisense ל-TypeScript, אז תוך כדי כתיבת הקוד מקבלים חיווי אם הקוד בסדר או שיש בו איזושהי בעיה. נכון להיום יש כבר הרבה editors שניתן להתקין בהם plugin שמספק intellisense ל-TypeScript, אבל ה-editor המומלץ ל-TypeScript הוא Visual Studio Code. ה-editor הזה גם הוא של מייקרוסופט אבל הוא open source וכמובן חינמי. ויש בו המון פיצ'רים באמת טובים. מומלץ לנסות להשתמש. אני עבדתי עד עכשיו עם Sublime ועברתי ל-Visual Studio Code ואני מאוד מרוצה מהשינוי הזה.

תמיכה ב-es6 - שפת TypeScript מאמצת במהירות פיצ'רים חדשים של es6 ותומכת בהם. ולכן כשמשתמשים ב-TypeScript ניתן להשתמש ביכולות החדשות של es6 והקומפיילר של TypeScript יתרגם אותם ל-JavaScript סטנדרטי של es3 או es5 כפי שנגדיר לו. וכך הקוד שלנו ירוץ בצורה טובה על כל הדפדפנים למרות שהם עדיין לא תומכים ב-es6 באופן מלא.

סינטקס דומה ל-JavaScript - הסינטקס של TypeScript דומה מאוד ל-JavaScript, כך שהמעבר מ-JS ל-TS לא קשה במיוחד. אם ניקח פרויקט קיים שכתוב ב-JS ונשנה את סיומת הקבצים שלו מ-js ל-ts יש סיכוי טוב שזה יתקמפל בהצלחה כבר בהתחלה. ואם יהיו בעיות, האדיטור יסמן לנו את כל המקומות הבעייתיים ונוכל לתקן את הקוד יחסית מהר.

תמיכה - ל-TypeScript יש תמיכה רצינית כיון שמאחורי הפיתוח שלה עומדת מיקרוסופט. הדבר הזה יכול להבטיח תמיכה לאורך זמן. בשונה משפות אחרות שמתקמפלות ל-JavaScript ומתוחזקות ע"י קהילת המפתחים בלבד, ל-TypeScript יש גם את קהילת המפתחים וגם את מיקרוספט. ניתן גם להתרשם מרמת התחזוקה של השפה ע"י התבוננות באתר הרישמי של TypeScript שם ניתן לראות תיעוד מסודר ורציני של השפה ודברים נוספים שמעידים על ההשקעה בפיתוח השפה.
רוב האדיטורים הנפוצים ביותר כיום תומכים ב-TypeScript. ביניהם כל משפחת Visual Studio כמובן, וגם Sublime, Atom, WebStorm, Eclipse, Emacs, Vim.

בנוסף לכל זה, TypeScript הופך להיות יותר ויותר נפוץ ולכן גם אם אתה לא משתמש בה בפועל מכל סיבה שהיא, מומלץ ללמוד ולהכיר אותה. יש כבר רשימה ארוכה של פרויקטים גדולים שמשתמשים ב-TypeScript ביניהם אנגולר 2.


צמיחה מהירה - אם נשתמש ב-Google Trends להשוואה של TypeScript מול שפות מתחרות כמו CoffeeScript ו-Dart נקבל את הגרף הבא:



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



כפי שרואים, TypeScript מובילה באופן משמעותי מול השפות המתחרות.

לסיכום

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

לכן, גם אם לא תשתמשו ב-TypeScript בפרויקט שלכם, עדיין מומלץ מאוד להכיר אותה.

בהצלחה.

כללי כתיבה ב-TypeScript



  • בעמוד הזה תמצאו כללים בכתיבת קוד TypeScript. 
  • הכללים כתובים בצורה קצרה כדי שיהיה אפשר למצוא במהירות את מה שחיפשנו. 
  • לכל כלל יש דוגמה קצרה לשיפור ההבנה ושיהיה אפשר לעשות העתק הדבק. 
  • אם יש כלל מסובך אז נוסיף גם דוגמה מפורטת יותר לצורך הסבר בהיר יותר.
  • הדף ילך ויתארך במשך הזמן... 😉
סוגי המשתנים
סוגי המשתנים הקיימים הם:
  • Boolean - משתנה בוליאני.

let firstTime: boolean = true;
  • Number - כל סוגי המספרים.

let decimal: number = 6; 
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
  • String - טקסט. אפשר להשתמש בגרש יחיד (') או גרשיים ("). ניתן גם להקיף את הטקסט ב-backtick (גרש אחורי שנראה כך `) כדי לכתובת טקסט על פני כמה שורות.

let firstName: string = "Yosef";
let firstName: string = 'Yosef';
let sixDayWar: string = `The Six-Day War, also known as the June
War, 1976 Arab-Israeli War, or Third Arab-Israeli War, was fought
between June 5 and 10, 1967 by Israel and the neighboring states of
Egypt, Jordan, and Syria`;
  • Array - ניתן להגדיר מערך מכל סוג. יש שני דרכים להגדיר מערך:

let oddNumbers: number[] = [1, 3, 5, 7, 9];
let oddNumbers: Array<number> = [1, 3, 5, 7, 9];
  • Tuple - מערך שמורכב מכמה סוגי משתנים.

let person: [string, number];
person = ["Avraham", 53];
אם נהפוך את הסדר נקבל שגיאה:

person = [53, "Avraham"]; // Error
ניתן להוסיף משתנים למערך שיהיו אחד מכל הסוגים שצויינו. בדוגמה שלנו ניתן להוסיף משתנים רק מסוג string או number :

person[2] = "Yaakov";
person[3] = 37;
person[3] = true; //Error - boolean is not string or number
  • Enum - דרך טובה לתת שמות למספרים:

enum days {Sunday, Monday, Tuesday, Wednesday};// 0,1,2,3
let today: days = days.Monday;
אם לא מציינים אחרת המיספור מתחיל מ-0. ולכן בדוגמא שלנו today יהיה שווה ל-1. ניתן לקבוע מאיזה מספר יתחיל המיספור:

enum days {Sunday = 1, Monday, Tuesday, Wednesday};// 1,2,3,4
וניתן גם לקבוע מספרים לכל שאר הערכים:

enum days {Sunday = 1, Monday = 2, Tuesday = 3, Wednesday = 4};
יש פיצ'ר שימושי ל-enum שניתן לקבל בקלות את השם של אותו האנומרציה:

enum days {Sunday = 1, Monday, Tuesday, Wednesday};
let todayName: string = days[2]; // todayName = "Monday"
  • Any - משתנה מכל סוג שהוא. נשתמש בסוג הזה כשאנחנו לא יודעים איזה סוג המשתנה הזה יהיה. זה בעצם דומה לכל המשתנים ב-JavaScript, שאין בדיקה של סוג הערכים ששמים לתוך המשתנים.

let whatever: any;
whatever = "good morning"; // OK
whatever = 7; // OK
whatever = false; // OK
זה גם שימושי למערך שאיננו יודעים מה יהיה בתוכו:

let supriseList: any[] = [53, "Avraham", true];
supriseList[1] = 39; // OK, since there is no type checking
  • Void - בדרך כלל משמש להגדיר שפונקציה מסוימת לא מחזירה ערך. אם מגידירים משתנה מסוג void אז הוא לא כל כך שימושי כי הוא יכול לקבל רק undefined או null.

let unusable: void = undefined;
  • Null and Undefined - שני הסוגים האלו הם בעצם גם הסוג של עצמם. הכוונה היא ש-null הוא מסוג null ו-undefined הוא מסוג undefined. אם נגדיר משתנה כאחד משני הסוגים האלו אז הוא לא יהיה כל כך שימושי כיון שיהיה אפשר להכניס אליו רק את אותו הסוג: null יקבל רק null, ו-undefined יקבל רק undefined.

let u: undefined = undefined;
let n: null = null;
שני הסוגים האלו הם subtypes של כל הסוגים האחרים. המשמעות היא שאפשר להכניס אותם למשתנה מכל סוג.
  • Never - הסוג הזה מייצג משהו שבעצם לא יכול לקרות. חייבים פה דוגמה כדי להבין במה מדובר. לדוגמה, פונקציה שלא חוזרת אף פעם תהיה בעלת סוג משתנה never ב-return value. גם never הוא subtype של כל שאר הסוגים, אבל שום סוג אחר לא יכול להיכנס למשתנה מסוג never (גם לא any). דוגמאות לפונקציות שסוג ה-return שלהם הוא never:

// Functions with unreachable end point
function error(message: string): never {
    throw new error(message);
}

function fail() {
    return error("Something failed");
}
function infiniteLoop(): never {
    while (true) {
    }
}

Type assertions


הביטוי type assertions דומה למה שנקרא casting. זו צורה להגיד לקומפיילר שיתייחס למשתנה מסוים לפי סוג מסוים. לדוגמה:

let someVar: any = "my string";
let strLen: number = (<string>someVar).length;

בצורה הזו אנו מודיעים לקומפיילר שיתייחס למשתנה someVar כ-string.
יש צורת כתיבה נוספת:

let someVar: any = "my string";
let strLen: number = (someVar as string).length;

let

ב-TypeScript משתמשים בעיקר ב-let כדי להגדיר משתנים במקום ב-var. 
להלן כמה נקודות חשובות בנוגע לשימוש ב-let:

  • משתנה שמוגדר ע"י let קיים רק בתוך אותו בלוק שבו הוא מוגדר (בשונה מ-var ששם ניתן להשתמש במשתנה בתוך כל אותה הפונקציה שבה הוא מוגדר). לדוגמה:

function f(input: boolean) {
    let a = 100;

    if (input) {
        // Still okay to reference 'a'
        let b = a + 1;
        return b;
    }

    // Error: 'b' doesn't exist here
    return b;
}

אם b היה מוגדר ע"י var היה ניתן להשתמש בו גם מחוץ ל-if כי הוא היה קיים בתוך כל הפונקציה f.

  • לא ניתן להשתמש במשתנה לפני שמגדירים אותו:

a++; // illegal to use 'a' before it's declared;
let a;

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


function foo() {
    // okay to capture 'a'
    return a;
}

foo(); // illegal call 'foo' before 'a' is declared

let a;

foo(); //legal call


  • לא ניתן להגדיר את אותו משתנה יותר מפעם אחת, גם אם פעם אחת משתמשים ב-var ופעם אחת ב-let. צריך לשים לב שאין התנגשות בין הפרמטרים שהפונקציה מקבלת לפרמטרים שמוגדרים בפונקציה. לדוגמה:

function f(x) {
    let x = 100; // error: x is a name of parameter in this function
}

  • בהמשך לסעיף הקודם, יש אפשרות להגדיר משתנה עם אותו שם של משתנה אחר בתנאי שעושים זאת בתוך בלוק פנימי יותר. דבר כזה נקרא shadowing. לדוגמה:

function f(condition, x) {
    if (condition) {
        let x = 100;
        return x;
    }

    return x;
}

f(false, 0); // returns '0'
f(true, 0);  // returns '100'

בדוגמה שלעיל, כיון שה-x השני מוגדר בתוך בלוק פנימי יותר (בתוך if במקרה הזה) אז זו כתיבה חוקית. בבלוק הפנימי x ייצג את ה-x הפנימי. מחוץ ל-if ה-x ייצג את הפרמטר x שהפונקציה קיבלה כ-input. זו דוגמה פשוטה ל-shadowing. כפי שניתן לראות, צורת כתיבה כזו אמנם חוקית אבל לא קשה להבנה וכדאי להימנע ממנה.


const

ב-TypeScript ניתן להגדיר קבועים ע"י שימוש ב-const.


const numDaysInWeek = 7;

אותם כללים של scoping של let חלים גם על const. ההבדל היחיד הוא שאי אפשר להכניס ל-const ערך מחדש. 
צריך להבין שאי אפשר לשנות את המשתנה שמוגדר כ-const, אבל את המבנה הפנימי שלו ניתן לשנות. לדוגמה:

const numLivesForCat = 9;
const kitty = {
    name: "Aurora",
    numLives: numLivesForCat,
}

// Error
kitty = {
    name: "Danielle",
    numLives: numLivesForCat
};

// all "okay"
kitty.name = "Rory";
kitty.name = "Kitty";
kitty.name = "Cat";
kitty.numLives--;



destructuring

אפשרות נוספת שיש ב-ECMAScript 2015, וזמינה ב-TypeScript נקראת destructuring. וכדי להבין את זה הכי טוב להשתמש בדוגמאות.
Array destructuring


let input = [1, 2];
let [first, second] = input;
console.log(first); // outputs 1
console.log(second); // outputs 2

בדרך הזו יוצרים שני משתנים first ו-second שכל אחד מקבל ערך מהמערך input לפי הסדר. 
ניתן גם להשתמש ב-destructuring עם משתנים קיימים. לדוגמה כדי להחליף ערכים בין משתנים (swap) ניתן לכתוב:



[x, y] = [y, x];

ניתן גם להשתמש בצורה הזו כדי להגדיר סוגי משתנים לפונקציה:

function f([first, second]: [number, number]) {
    console.log(first);
    console.log(second);
}
f(input);

ניתן גם להגדיר משתנה שיקבל את כל שאר הערכים ע"י שימוש ב-"..."


let [first, ...rest] = [1, 2, 3, 4];
console.log(first); // outputs 1
console.log(rest); // outputs [ 2, 3, 4 ]

או לקחת רק ערך אחד:


let [first] = [1, 2, 3, 4];
console.log(first); // outputs 1

או ערכים במיקומים מסוימים:


let [, second, , fourth] = [1, 2, 3, 4];



Object destructuring
שימוש דומה ניתן לעשות גם עם אובייקטים. לדוגמה:


let o = {
    a: "foo",
    b: 12,
    c: "bar"
}
let { a, b } = o;

בדוגמה זו נוצרים 2 משתנים a = o.a, b = o.b. כפי שרואים, אין חובה להשתמש ב-c אם אין צורך. מה שאנחנו עושים פה זה בעצם פירוק של האובייקט למשתנים. ולכן השמות של המשתנים צריכים להיות כמו שמות השדות של האובייקט. אם ננסה לעשות השמה למשתנים עם שמות אחרים נקבל שגיאה, למשל:


let {a, d} = o;// Error, d is not property of o

אם רוצים לשנות את שמות המשתנים שלא יהיו זהים לשמות שיש באובייקט ניתן לעשות זאת כך:


let { a: newName1, b: newName2 } = o;

כך ניצור שני משתנים חדשים newName1 = o.a, newName2 = o.b. צריך לשים לב שהנקודתיים לא מגדירים את סוג המשתנה כמו בדרך כלל. 
אם רוצים גם להגדיר את סוג המשתנה נעשה זאת כך:


let { a: newName1, b: newName2 }: { a: string, b: number } = o;

אבל אין צורך להגדיר את סוג המשתנה כיון ש-newName1 יקבל את הסוג של a, ו-newName2 יקבל את הסוג של b.
בדומה למערכים, ניתן לעשות destructing גם למשתנים קיימים. לדוגמה:


({ a, b } = { a: "baz", b: 101 });

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


let { a, ...rest} = o;
console.log(rest.b);// print 12
console.log(rest.c);// print "bar"

ניתן גם לקבוע default value למקרה שיש צורך לדוגמה:


function keepWholeObject(myObj: { a: string, b?: number }) {
    let { a, b = 1001 } = myObj;
}

בדוגמה הזו, אם יקראו לפונקציה עם אובייקט שאין לו b אז המשתנה b שבתוך הפונקציה יקבל ערך דיפולטיבי של 1001.


Function declarations

ישנם מספר שימושים אפשריים ב-destrcting בפונקציות.

1. הגדרת סוג הפרמטרים של הפונקציה:


type C = { a: string, b?: number }
function f({ a, b }: C): void {
    // ...
}

בדוגמה זו, הפרמטר a של הפונקציה יהיה מסוג string, והפרמטר b יהיה אופציונלי ומסוג number.

2. השמה של default values:


function f({ a, b } = { a: "", b: 0 }): void {
    // ...
}
f(); // ok, default to { a: "", b: 0 }

בדוגמה זו, הפרמטר a יהיה מסוג של המשתנה a שבצד ימין ויקבל ערך default של מחרוזת ריקה. הפרמטר b יהיה מסוג של המשתנה b שבצד ימין ויקבל ערך default של 0.
אם יש צורך שאחד המשתנים יהיה אופציונלי אז צריך לתת לו ערך default בהגדרת המשתנים (בצד שמאל) ולא בהגדרת ה-destructing (צד ימין). לדוגמה:



function f({ a, b = 0 } = { a: "" }): void {
    // ...
}
f({ a: "yes" }) // ok, default b = 0
f() // ok, default to { a: "" }, which then defaults b = 0
f({}) // error, 'a' is required if you supply an argument


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



Spread


האופרטור spread מאפשר הכנסה של ערכי מערך אחד למערך שני או הכנסה של ערכי אובייקט אחד לאובייקט שני.
דוגמה למערכים:


let first = [1, 2];
let second = [3, 4];
let bothPlus = [0, ...first, ...second, 5];// bothPlus will be [0,1,2,3,4,5]

דוגמה לאובייקטים:


let defaults = { food: "spicy", price: "$$", city: "Jerusalem" };
let search = { ...defaults, food: "sweet" };

השימוש של spread באובייקטים מורכב יותר מהשימוש במערכים. אם יש property שמופיע פעמיים, אז מתחילים משמאל לימין והימני יותר דורס את השמאלי. ולכן בדוגמה הזו התוצר הסופי יהיה:

search = { food: "sweet", price: "$$", city: "Jerusalem" };
אם היינו כותבים:

let search = { food: "sweet", ...defaults};
אז:

search = { food: "spicy", price: "$$", city: "Jerusalem" };

ישנה מגבלה של spread לגבי אובייקטים שמכילים פונקציות. הפונקציות לא עוברות לאובייקט החדש. לדוגמה:


class C {
  p = 12;
  m() {
  }
}
let c = new C();
let clone = { ...c };
clone.p; // ok
clone.m(); // error!


Union


ב-TypeScript ניתן להגדיר משתנה שיכול להיות אחד ממספר סוגים. לדוגמה:


x = number | string;
x = 3; // OK
x = "whatever"; // OK


בדוגמה הזו, x יכול להיות number או string.
למשתנים מסוג union יש גישה רק לדברים שמשותפים לכל הסוגים, לדוגמה:


interface dog{
    eat();
    bite();
}

interface bird{
    eat();
    fly();
}

function getAnimal(): dog | bird {
    // ...
}

let myAnimal = getAnimal();
myAnimal.eat(); // okay
myAnimal.fly(); // error


Intersection

ב-TypeScript ניתן להגדיר משתנה שיכיל מספר סוגים של משתנים אחרים. בשונה מ-union, אם רוצים להגדיר intersection צריך להגדיר את כל מה שיש בכל הסוגים שבהם השתמשנו. לדוגמה:


interface dog{
    legs: number;
    tail: boolean;
}

interface bird{
    legs: number;
    wings: boolean;
}

let Animal: dog & bird = {
    legs: 4,
    tail: true,
    wings: false
}

אם לא הייתי מגדיר ב-Animal את wings לדוגמה, הייתי מקבל שגיאת קומפילציה.

Interfaces


ב-TypeScript


String literal types

ב-TypeScript ניתן לקבוע סוגי משתנים ע"י שימוש בערכים מסוימים ולא רק בסוגים כלליים. לדוגמה:

let color: "Black";
color = "Black";
color = "White";// error!

השימוש בזה יותר הגיוני ביחד עם Union. למשל:

let color: "Black" | "White";
color = "Black";
color = "White";// OK


בצורה זו ניתן לקבוע שלמשתנה מסוים יהיו רק כמה אפשרויות מסוימות ולא כל האפשרויות של אותו סוג כגון string.


Type Aliases


ניתן לתת שם ל-type מסוים. האפשרות הזו בד"כ משלימה את ה-string literal types מהפיסקה הקודמת. אם נחזור לדוגמה הקודמת אז יהיה יותר יפה לכתוב אותה כך:


type optionalColors = "Black" | "White";
let color: optionalColors;
color = "Black";
color = "White";


משתמשים במילה type כדי לתת שם לסוג מסוים שרוצים ליצור.


Polymorphic this types

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


class Animal{
    saySomthing(){
 //some code here
 return this;
    }
}

class Cat extends Animal{
    eatMouse(){
 //some code here
 return this; 
    }
}

class Dog extends Animal{
    eatCat(){
 //some code here
 return this; 
    }
}

let c = new Cat();
let d = new Dog();

c.saySomthing();// returns Cat
d.saySomthing();// returns Dog

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


Declaration merging


הקומפיילר של TypeScript יאחד כל מיני הגדרות אם יש להם שם זהה. לדוגמה אם במקום אחד בקוד כתוב:

interface Car {
    name: string;
    drive: () => void;
}


ואילו במקום אחר כתוב:


interface Car {
    id: number;
    model: string;
}

אז הקומפיילר יאחד את שתי ההגדרות ובפועל יהיה לנו משהו כזה:


interface Car {
    name: string;
    drive: () => void;
    id: number;
    model: string;
}

לא כל דבר אפשר לאחד. 
הדברים שאפשר לאחד הם:
  • Interfaces
  • Enums
  • Namespaces
  • Namespaces with class
  • Namespaces with functions
  • Namespaces with enums

הדברים שאי אפשר לאחד הם:
  • Classes with classes

Type guards


ה-type guards מאפשרים בדיקה של סוג המשתנה. השימוש ב-type guards מאפשר לקומפיילר לצמצם את סוג המשתנה לסוג ספציפי. היתרון בזה הוא שהקומפיילר יכול למצוא יותר שגיאות על פי סוג המשתנה.

typeof

let x: string | number = 343;
if (typeof x === 'string') {
    // x is a string
}
else {
   // x is a number
}


ניתן להשתמש רק עם: "string", "number", "boolean", "symbol"
instanceof

class Cat {
   // some code here
}

class Dog {
   // some code here
}

let pet: Cat | Dog = new Cat();

if (pet instanceof Cat) {
    // pet is a Cat
}


הבדיקה של Instaceof מתבצעת בין המשתנה לבין constructor function של מחלקה מסוימת.

user-defined type guard


interface Animal {
   numberOfLegs: number;
}
function isAnimal(a: any): a is Animal {
    return (<Animal>a).numberOfLegs !== undefined;
}
let d = new Dog();
if(isAnimal(d)) {
    //it's an animal
}

הסינטקס של ה-return value של isAnimal (בדוגמה שלנו "a is Animal") זה מה שהופך את הפונקציה הזו ל-type guard. בעצם אנחנו בודקים את הפרמטר של הפונקציה מול הסוג שאנחנו רוצים. המילה is היא מילה שמורה ב-TypeScript.


Symbols

ה-symbol זה סוג חדש של משתנה בסיסי שהוכנס החל מ-ES2015.
ה-symbol הוא unique (כל symbol שונה משאר ה-symbols), ו-immutable (ברגע ש-symbol נוצר לא ניתן לשנות אותו).
ב-TypeScript יש תמיכה ב-symbol אבל צריך לשנות בקובץ tsconfig.json את השדה "target" ל-"es2015" כדי שיתמוך בסוג הזה.


let mySymbol = Symbol('some_optionally_description');//the description is 
// optionally and it can be string or a number

שימוש ב-symbol כ-key באובייקט:


let myObject = {
[mySymbol]: 'value for my symbol'
}

console.log(myObject[mySymbol]);

 שימוש ב-symbol כשם של פונקציה במחלקה:


export const MY_FUNC = Symbol();

class myClass {
    [MY_FUNC]():void {
        console.log('this is a print from function');
    }
}

בקובץ שבו נשתמש בפונקציה הזו נצטרך לפני זה לייבא את MY_FUNC ואז נוכל להשתמש:



import { MY_FUNC } from './file_path/file_name';

let x = new myClass();

x[MY_FUNC]();

ישנה גם אפשרות לשנות את ההתנהגות הפנימית של שפת TypeScript ע"י שימוש ב-symbol. הצורך בזה הוא נדיר ולכן לא נכתוב עליו ורק נזכור שיש כזאת אפשרות וניתן למצוא אותה באתר הרשמי.



Decorators

  • ה-decorators הוא פיצ'ר עתידי שאמור להיות ב-JS אבל כבר זמין ב-TypeScript. 
  • הוא יכול להתקמפל ל-ES5 כבר כיום.
  • ניתן לכתוב decorator ל-classes, methods, accessors, properties and parameters
  • כדי להפעיל decorator פשוט כותבים @ ולאחריו את שם ה-decorator בשורה שמעל הדבר שעליו רוצים להפעיל את ה-decorator 
  • ניתן לדעת מה סוג ה-decorator לפי החתימה של הפונקציה (מהם הפרמטרים שהי מקבלת ומה סוג ההחזר שלה).
  • אם ה-decorator ממומש בקובץ אחר צריך לייבא אותו לקובץ שבו רוצים להשתמש בו בצורה הבאה:
import {decoratorName} from 'pathToDecoratorFile';
  • כדי להשתמש ב-decorators צריך להוסיף ל-tsconfig.json את הדבר הבא:
tsconfig.json

{
    "compilerOptions": {
 "experimentalDecorators": true
    }
}



Class decorator

יש שני סוגים של-class decorator. הראשון לא משנה את ה-constructor function והשני משנה את ה-constructor function.

דוגמה לסוג הראשון:
function decoratorName(target: Function): void{
 /*do something*/
}

ניתן לראות שמדובר ב-class decorator כיון שהפונקציה מקבלת רק פרמטר אחד והפרמטר מסוג Function. ניתן לראות שמדובר על הסוג הראשון (שלא משנה את ה-constructor function) מכך שהפונקציה מחזירה void. 
הפרמטר שהפונקציה מקבל הוא ה-constructor של ה-class.
כדי להשתמש ב-decorator הזה פשוט נכתוב @ ואת שם ה-decorator לפני ה-class שעליו נפעל. לדוגמה:


@decoratorName
class className{

}

אנחנו לא צריכים לתת ל-decorator את הפרמטר שהוא מקבל, TypeScript עושה את זה בצורה אוטומטית.

דוגמה לסוג השני:

function decoratorName<TFunction extends Function>(target: TFunction): TFunction {
 let newConstructor: Function = function () {
  //do something
 }
 newConstructor.prototype = Object.create(target.prototype);
 newConstructor.prototype.constructor = target;
 return <TFunction>newConstructor;
}

Class decorator factory
ה-decorator factory הוא בעצם decorator דינאמי שמקבל ערך. 
דוגמה ל-class decorator factory מהסוג הראשון:

function decoratorName(name: string) {
 return function(target: Function): void{
 /*do something*/
 console.log('the name is : ${name}');
 }
}

@decoratorName('someName')
class className{

}

בדוגמה זו ניתן לראות הגדרה של class decorator factory ושימוש בו. ניתן גם לראות שימוש בפרמטר שה-decorator קיבל (name).

Property decorator

ה-property decorator מקבל שני פרמטרים. 
הראשון הוא ה-constructor של ה-class שה-property שייך אליה, אם ה-property הוא static. אחרת הוא יהיה ה-prototype של ה-class שה-property שייך אליה.
הפרמטר השני זה שם ה-property שעליו ה-decorator פועל.
גם ב-property decorator  כדי להשתמש בו רק כותבים מעל ה-property שרוצים להפעיל עליו את ה-decorator את הסימן @ ולאחריו שם ה-decorator. אין צורך להעביר אליו שום פרמטר כי TypeScript עושה זאת באופן אוטומטי.
ה-property decorator  נראה משהו כזה:


function MyPrpertyDecorator (target: Object,
                      PropertyName: string) {
 // do something
}



Parameter decorator

ה-parameter decorator מקבל שלושה פרמטרים. 
הראשון והשני דומים ל-property decorator.
הראשון הוא ה-constructor של ה-class שה-property שייך אליה, אם ה-property הוא static. אחרת הוא יהיה ה-prototype של ה-class שה-property שייך אליה.
הפרמטר השני זה שם ה-property שעליו ה-decorator פועל.
הפרמטר השלישי הוא האינדקס של הפרמטר שעליו נפעיל את ה-decorator. האינדקס הכוונה למה מספר הפרמטר ברשימת הפרמטרים שיש לפונקציה שעליה נפעיל את הdecorator. הראשון יהיה מספר 0 השני יהיה 1 וכן הלאה.
ה-parameter decorator  נראה משהו כזה:


function MyParameterDecorator (target: Object,
                          PropertyName: string,
                          parameterIndex: number) {
 // do something
}



Method decorator

ה-method decorator מקבל שלושה פרמטרים. 
הראשון והשני דומים ל-property decorator.
הראשון הוא ה-constructor של ה-class שה-property שייך אליה, אם ה-property הוא static. אחרת הוא יהיה ה-prototype של ה-class שה-property שייך אליה.
הפרמטר השני זה שם ה-property שעליו ה-decorator פועל.
הפרמטר השלישי הוא ה-property descriptor של ה-method שעליו נפעיל את ה-decorator (ה-property descriptor זה פיצ'ר של JS שהתווסף ב-ES5 והוא בעצם אובייקט שמתאר property מסוים ואיך אפשר לעבוד איתו). אנחנו צריכים אותו כי הוא שולט על ה-method ובעזרתו נוכל לשלוט על ה-method ב-decorator שניצור. לדוגמה נוכל להפוך את ה-method ל-read only.
דוגמה ל-method decorator factory שקובע אם ה-method הוא read only או לא:


function readOnly (isReadOnly: boolean) {
 return function (target: Object,
                  PropertyName: string,
                  descriptor: PropertyDescriptor) {
  descriptor.writable = isReadOnly;
 }
}

@readOnly(true)
functionName{

}

בדוגמה ניתן לראות הגדרה של method decorator factory  ושימוש בו. אם ננסה לשנות את ההגדרה של הפונקציה functionName ב-run time נקבל error.


Callbacks

כדי לכתוב קוד אסינכרוני נשתמש בפונקציות callback. קוד אסינכרוני חשוב לטובת חווית המשתמש כדי שלא יווצר מצב שהתוכנה ניתקעת ומחכה לתשובה מאיזשהו מקום. ברגע שהתשובה מגיעה ניקרא לפונקציית ה-callback ונשתמש במה שהתקבל.
פונקציית callback היא פונקציה רגילה. עם זאת ישנה מוסכמה (בעיקר בקהילת ה-node) שפונקציות callback מקבלות 2 פרמטרים. הראשון הוא אובייקט error והשני הוא ה-data שהפונקציה צריכה להשתמש בו.
דוגמה לשימוש ב-callback בפונקציה אסינכרונית:


interface someCallbackInterface {
 (err: Error, someValue: number): void;
}

function funcName(someData, callback:someCallbackInterface):void {
 returnValue = someAsyncFunction(someData);
 try{
  if(returnValue != -1){
   callback(null, returnValue);
  }
  else{
   throw new Error('some error');
  }  
 } catch(error) {
  callback(error, null);
 }
}

function myCallbackFunction(err: Error, someValue: number):void {
 if(err) {
  console.log('the error is: ${err.message}');
 }
 else {
  console.log('the return value is: ${someValue}');
 }
}

console.log('before the call to async function');
funcName(someData, myCallbackFunction);
console.log('after the call to async function');

בדוגמה הזו שני ההדפסות ל-console יתבצעו לפני ההדפסה של פונקציית ה-callback. ולכן נקבל משהו כזה:
before the call to async function
after the call to async function
the return value is: 5
או במקרה של error:
before the call to async function
after the call to async function
the error is: some error

Promises

כדי להשתמש ב-promises צריך לקנפג את ה-TypeScript להתקמפל ל-ES2015. בקובץ tsconfig.json נשנה את השדה "target" ל-"es2015".


ה-constructor של ה-promise מקבל פונקציה. הפונקציה הזו מקבלת 2 פרמטרים.


function funcName(resolve, reject) {
 //make some async calls
 if (success) 
    resolve(data);
 else 
    reject(reason);
}

let p: Promise<string> = new Promise(funcName);

הפרמטרים resolve ו-reject הם פונקציות שה-promise מבין.
אם הפעולה האסינכרונית בוצעה בהצלחה, קוראים לפונקציה resolve עם ה-data שאנחנו רוצים להעביר הלאה למי שישתמש בו.
אם הפעולה האסינכרונית נכשלה, קוראים לפונקציה reject עם הסיבה לכישלון.
הסוג של ה-promise, במקרה הזה <string>, הוא סוג ה-data שנשלח בפונקציה resolve במקרה שהפעולה האסינכרונית הצליחה.

צורה נפוצה יותר להגדרת promise נראית ככה:


let p: Promise<string> = new Promise((resolve, reject)=> {
 //make some async calls
 if (success) 
    resolve(data);
 else 
    reject(reason);
});

הפעולה על תוצאת ה-promise מתבצעת על ידי 2 פונקציות: then ו-catch.


p.then(data => console.log(data))
 .catch(reason => console.log(reason));

אם הפעולה האסינכרונית בוצעה בהצלחה, הפונקציה then תתבצע וה-data יהיה מה שנשלח ב-resolve. הפונקציה then גם היא מחזירה promise כך שבמקרה של כישלון נוכל לתפוס את הבעיה ע"י שימוש ב-catch. גם במקרה שהפעולה האסינכרונית הצליחה אבל נזרק exception מתוך ה-then גם אז הבעיה תיתפס בפונקציית ה-catch. ה-reason הוא הפרמטר שנשלח מהפונקציה reject.
ישנה דרך נוספת לטפל בכישלון של promise:


p.then(data => {
 console.log(data)
  }, reason => {return 0;})
 .catch(reason => console.log(reason));

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


async/await


המימוש של async/await נעשה תוך שימוש ביכולות שהוכנסו ב-ES2015 ולכן כדי להשתמש ב-async/await צריך לקנפג את ה-TypeScript להתקמפל ל-ES2015. בקובץ tsconfig.json נשנה את השדה "target" ל-"es2015".
כל פונקציה שמשתמשת ב-await חייבת להיות מוגדרת עם async לפניה. כל פונקציה שמוגדרת עם async/await מחזירה promise.
דוגמה:


async function someAsyncJob() {
 let result = await getDataFromDb ();
 console.log('the result is: ${result}');
}

console.log('before the call to async function');
someAsyncJob()
 .catch(reason => console.log(reason));
console.log('after the call to async function');

מה שיקרה כאן זה כשנגיע לקריאה ל-getDataFromDb (זו פונקציה שמחזירה promise) נעצור עד שנקבל תשובה. שזה נשמע כמו קוד סינכרוני. אבל מצד שני הפונקציה someAsyncJob מוגדרת עם async שזה אומר שזו פונקציה אסינכרונית. ולכן הקוד הראשי שלנו הוא אסינכרוני והוא לא יתקע כשהוא מחכה לתשובה מה-DB. בתוך הפונקציה someAsyncJob יש קוד סינכרוני שמחכה עד שהוא מקבל תשובה מה-DB ורק אז ממשיך לרוץ.

כיון שפונקציה שמוגדרת עם async/await מחזירה promise ניתן לטפל בהחזר שלה בצורה דומה ל-promise ע"י catch כדלעיל.