2019-06-01

שיעור מס' 8 בהורשה


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

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

לצורך הדיון אני אציג רק ממשקים, ולא אתעכב על חלוקה ל Interfaces ו Concrete classes (שהוא באמת משני לדיון).


בואו נתחיל.


הבעיה


דמיינו שיש לנו תוכנה שדורשת לצייר צורות, את הצורות מתארים ע"פ הממשק הבא:


מיקום הצורה (x, y), לצורך הדיון - הוא לא חלק מההגדרה.

משהו לא מריח כאן טוב בהגדרה הזו...
יש משהו מאוד פרוצדורלי, ולא כ"כ OO בהגדרה הזו: ה Path נראה כמו מרכיב זיהוי מרכזי בהתייחסות לצורה - אבל הוא מיוצג כתכונה כשאר התכונות.

למשל: אנו יכולים לייצר Line עם fillColor - מצב לא תקין. הכוונה להשתמש ב fillColor היא רק עבור פוליגון.

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

כיצד ניתן למדל את האובייקט בצורה יותר נכונה?

יש לכם איזה רעיון?!



פתרון ראשון




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

בואו נבחן את המודל שלנו עכשיו:
  • DRY (חוסר חזרה על הקוד) - מצב מצוין. אין באמת הרבה מה לקצר את הקוד.
  • הכמסה (encapsulation) - מצב בינוני. על הפשטת העל יש כמעט את כל התכונות, מה שאומר שכל מי ש"אוחז" ברפרנס ל BaseShape יקבל גישה לכל התכונות הללו. האם יש לזה צורך? או האם "שיתפנו" יותר מדי?
    הנחת היסוד היא שהשימוש האחיד באובייקטים הוא בעזרת הפונקציה ()draw. נניח: רוצים לרוץ על מערך ולצייר את כולם, אך בכל מקרה, כל גישה מעמיקה יותר דורשת את בדיקת הטיפוס ו downcasting. 
    • אין נזק מיידי באי-הכמסה ("אז שלא ישתמשו בזה! מה הבעיה?!") - אבל זה זרז לשימוש לא מבוקר בתכונות הללו. מפתח שמקבל הצעה לשימוש בתכונה ב autocomplete של ה IDE - לעתים רחוקות ישאל את עצמו אם נכון היה שהאובייקט יחשוף תכונה זו, או האם בכלל נכון להשתמש בה.
    • כל מפתח יכול להיכנס לאובייקט ולשנות את כל שדות האובייקט ל Public - אבל זה סייג המעורר הערכה מחודשת על נכונות הפעולה (ברוב, התקין, של המקרים).
  •  אמינות המודל למציאות / פשטות הבנה (presentation) - הרבה יותר טוב, אך עדיין ישנם עיוותים. 
    • הסרנו את ה Path כתכונה, ופתרנו את הבעיה בה fillColor זמין להיכן שאינו רלוונטי - שזה מצוין. 
    • שמות המשתנים אינם טובים. הם "פשרה" עבור "שימוש חוזר בקוד" / או סתם מכח האינרציה - פשרה לא טובה.

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

  • אנו מספקים את הצורך הבסיסי של המערכת ביישות לאחסן בה נתונים / להגדיר פונקציות. לחלופין זה היה יכול להיות מערך גלובאלי של נתונים. זו חובה כדי שהקוד יעבוד - אך זה קו האפס מבחינת הנדסת תוכנה.
  • אנו מתארים כוונה / רעיון - שאנו רוצים להנחיל לשותפנו לבסיס-הקוד (להלן Presentation), ובתקווה שקבענו את הכוונה / רעיון בצורה טובה ואמינה לעולם העסקי הרלוונטי.
  • אנו מגבילים את חשיפת הידע במערכת, בעזרת הכמסה - אם אנחנו עובדים לפי מודל Object Oriented.
  • כן, פרקטיקה טובה של קוד, היא לא לשכפל קוד (DRY = Don't Repeat Yourself) - אבל זו לא ממש מטרה בהגדרת אובייקט.

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

כדי לחדד מדוע האובייקטים בדוגמה שלנו אינם ממודלים בצורה טובה, נשטח שנייה את האובייקטים מהיררכיית שבה הם בנויים, ונציג אותם כפי שיופיעו ב run-time, או למשל - ב IDE כאשר אנו מקבלים המלצות שימוש ב autocomplete:


אם אנו בוחנים האובייקטים, אחד אחד - אלו לא אובייקטים כפי שהיינו מגדירים לו היו בלתי-תלויים.

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

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

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

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


פתרון שני



אתם בוודאי רואים שהאובייקטים מתוארים עכשיו בצרה טובה יותר, כאשר אנחנו מנתקים מעט מהקשר ביניהם - ומגדירים אותם אחד אחד.

עבור Point, הרבה יותר מדויק להסביר שתכונה מסוימת היא קוטר (או לחלופין זה יכול היה להיות רדיוס) - ולא "line width", שמשאיר מקום לפרשנות. בוודאי שיותר טבעי לי לחשוב על color ולא "line color" - כי אין קו בנקודה.
ב Polygon יותר מדויק לדבר על Border ולא Line, עניין של דייקנות - שיכולה להשתלם מאוד לאורך חיי-מערכת.

שיתפנו בין האובייקטים רק את מה שהכרחי: הפונקציה ()draw (לצורך ריבוי-צורות).

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

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

היתרון היחידי של הפתרון הקודם היה צמצום של כמה שורות קוד - כאין וכאפס מול מודל ברור יותר.

ממשקים הוא כלי שתכליתו היא PolyMorphism (ריבוי-צורות): התייחסות אחידה לאובייקטים שונים ע"פ מכנה משותף משמעותי. אל תתלו עליהם משימות של Code-Reuse, כי זה לרוב ייגמר בפשרה לא-משתלמת..

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



סיכום


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

DRY היא פרקטיקה חשובה מאוד - אבל יותר באזור של תוכן הפונקציות: יש כמה שורות קוד שחוזרות על עצמן? הקפידו להוציא אותן לפונקציה משותפת.
יש משתנים כפולים (או state כפול כלשהו)? אבוי - היפטרו מהעותקים מיד! ("data duplication is the mother of all Evil").

זכרו, שגם במקרים כאלו - DRY לא תמיד נכון. אם יש 5 שורות קוד שחוזרות על עצמן - אך מבטאות צרכים שונים, כנראה שלא כדאי לשתף אותן. אחד הצרכים עתיד להשתנות - ו coupling בין מימוש הצרכים, יקשה על השינוי הזה.
(הפתרון פשוט: פיצול הפונקציה ל-2 גרסאות, אחת לכל צורך - אבל הרבה פעמים אנו מפספסים את זה, ומסבכים את המערכת).


ייתכן ואתם שואלים את עצמכם "אם זה שיעור מס' 8 בהורשה - מהו שיעור 7? ". טוב, נו - זו לא הייתה כותרת מדויקת. ניסיתי לבטא שזה שיעור חשוב ב Object-Oriented (ולאו דווקא הורשה), אבל לא השיעור הראשון והחשוב ביותר. וגם לא הבסיסי ביותר. אני מדמיין אותו ... כאיפשהוא כשיעור מספר 8, מבלי מחויבות גבוהה לדיוק.


שיהיה בהצלחה!



2019-05-29

JavaScript ES6/7/8 - להשלים פערים, ומהר - חלק ג' ואחרון


פוסט שלישי ואחרון בסדרה:
  • בחלק הראשון - כיסינו את let ו const, חידושים בגזרת הפונקציות, ו spread operator השימושי.
  • בחלק השני - כיסינו Classes ו Modules - וגם הזכרנו בקצרה יכולת ליבה חשובה בשם Promises.
בחלק הזה, נכסה עוד כמה אלמנטים בתחביר שעשויים להיות לא-מובנים, נכסה יכולות מתקדמות כמו Symbols ו Generators - ונסיים ב Async-Await - המנגנון שמפשט את השימוש ב Promises, וכנראה שנשתמש בו ברוב המקרים.


בואו נתחיל!



שימושים שונים ל [ ]


ב ES5 אנו רגילים לסוגריים המרובעים - כמגדירים של רשימה. ב ES6 נראה אותם בעוד כמה וריאציות. למשל:


אמנם Array בשפה הוא iterable, אבל כאשר אנו רוצים להפעיל לולאה עם האינדקס, אנו משתמשים בפונקציה בשם ()entries המחזירה iterator של האיברים עם האינדקס שלהם. כלומר: בכל אינטרקציה אנו נקבל מערך [index, item]. מכאן הכי טבעי להשתמש ב deconstructing assignment על מנת לקבל את הערכים.

שימו לב ש for x... of XList הוא פורמט חדש ללולאה, המבוססת על iterables. ברור.



Computed property names

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

  1. יצרנו לאובייקט תכונה ששמה מגיע ממשתנה חיצוני - ולכן עשוי להשתנות עם הזמן.
  2. הנה אנחנו ממש מפעילים חישוב כדי לבנות את שם ה property. אפשר ומותר.
  3. האובייקט שנוצר - מייצג את תוצאות החישוב.
    1. הנה אפשר לגשת לתכונות ששמן נוצר ע"י חישוב - באופן רגיל.
    2. ניתן גם להשתמש בתחביר הסוגריים המרובעים לשלוף תכונה מתוך האובייקט. האמת, שגם ב ES5 ניתן לגשת לתכונה עם שם דינאמי באובייקט, אם כי בצורה קצת אחרת: פשוט צריך להרכיב מחרוזת של השם שלה.
    3. התחביר של computer property name עובד רק ב context של אובייקט. ב contexts אחרים, לסוגריים מרובעים יהיו משמעויות אחרות.

אז מה בעצם השימוש לתחביר החדש הזה של computer properties? האם TC39 ניסו בכח לבלבל אותנו עם משמעויות שונות לסימנים מוכרים?!

אני מקווה שלא.

הנה שימוש טיפוסי אחד: יצירה דינאמית של אובייקטים (או Classes):


וריאציה נוספת, וחשובה יותר של השימוש - קשורה לאייטם הבא.



Symbols

ל ES6 נוסף טיפוס פרימיטיבי חדש בשפה, בשם Symbol.

אולי אתם מכירים Symbol מ Ruby, Elixier או Closure (או Scala. הכל יש בסקאלה). אמנם יש דמיון, אבל זה לא אותו הדבר. ב ES6 השימוש הוא שונה, ובעיקר סובב סביב הוספת metadata לאובייקטים, והוספת יכולות חדשות לשפה בצורה תואמת-לאחור.

נתחיל בדוגמה:


באובייקט customer1 הגדרנו 2 שדות: שם ומזהה לקוח. משום מה, שדה ה customerId לא זמין לרוב הפעולות בשפה - אנחנו רואים רק את השדה name. מוזר!

הפונקציה ()Symbol מייצרת עבורנו מופע חדש (וייחודי, Singleton) של symbol. המחרוזת שמועברת לה - משמשת כמזהה ל Symbol.

Symbol נשמע כ property "בלתי נראה" על האובייקט. לא ניתן property שהוא Symbol מעזרת מפתח שהוא מחרוזת, והוא לא יתנגש עם properties עם "שם" זהה (כפי שאמרנו, אין לו שם). אפשר לחשוב על Symbols כ properties ביקום מקביל.

על Symbol ניתן לבצע את כל הפעולות הרגילות על properties כגון הוספה ומחיקה - כל עוד מעבירים את ה symbol לפעולה (הוספה, מחיקה, וכו'). כל פעולה על Symbol דורשת התייחסות למופע של ה Symbol.

  1. אפשר לשלוף ערך מאובייקט - ע״פ ה Symbol. אם לא קיים - יחזור undefined.
  2. אפשר לקבל את כל שמות ה Symbols על אובייקט - בעזרת הפונקציה ()Object.getOwnPropertySymbols
  3. למרות שהגדרנו 2 symbols שונים עם אותו "מזהה" CUSTOMER_ID - הם שונים. המזהה שלהם הוא המופע של פרמיטיב ה Symbol - ויש לעשות בו שימוש-חוזר! עובדה: יש לנו 2 symbols עם אותו "מזהה" על האובייקט - אבל הם בעצם Symbols שונים לחלוטין.

בלי להרחיב יותר מדי, אציין רק של Symbols יש global registry שדרכו אפשר לקבל את ה instance (הייחודי, singleton) של Symbol מסוים - מבלי ליצור תלויות של כל הקוד בקובץ יחיד. הפונקציה בעזרתה מקבלים מופע של Symbol נקראת (...)Symbol.for. לרוב מי שישתמש ב Symbols הם Frameworks. אם אתם רוצים להבין Symbols יותר לעומק - אני ממליץ על המאמר המקיף הזה.

שימוש חשוב נוסף של Symbols הם לספק שמות חדשים בשפה, שלא יתנגשו בשמות קיימים בקוד קיים. למשל: רצו להוסיף יכולת להגדיר אובייקטים כ Iterable ע"י מימוש של פונקציה עם שם מסוים - אך כל שם בעולם שיבחרו (כולל zz3pro) - עשוי להיות כבר בשימוש ע"י קוד ג'אווהסקריפט (זוכרים את חוק אטווד?).

הפתרון הוא בשימוש ב symbols - כך שלא תהיה התנגשויות. בכדי להגדיר אובייקט כ Iterable עלינו לממש פונקציה בשם ()[Symbol.iterator], כאשר Symbol.iterator הוא Symbol של השפה. פונקציות הרי רשומות כ property על האובייקט (או prototype) - וה property הזה יכול להיות גם Symbol.



Generators


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

Generators מייצרים פונקציות שלא מבוצעות במלואן בקריאה אליהן - אלה רק בסדרה של קריאות, בעצם סוג של iterator. הסימון שלהן בשפה: *function.

כלומר: Generator הוא פונקציה שמייצרת iterator.

מה השוני שלהן מ [Symbol.iterator] או מפונקציה שמחזירה פונקציה? יש לפונקציה שנוצרה ע״י Generator כמה תכונות מיוחדות ומתקדמות - המנוהלות ברמת המפרשן. השימוש העיקרי של Generators הוא לנהל בצורה קלה לקריאה סדרה של פעולות, בד"כ אסינכרוניות - בעלות קשר אחת לשנייה.

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


ה Generator מייצר את ה iterator מאחורי הקלעים. גוף הפונקציה שלו - היא בעצם ה template לקוד של כל iterator - כאשר המפרשן מנהל את ה state של ה iterator הזה: הוא זוכר היכן בדיוק עצרנו, ואת ערכי המשתנים ב scope הפונקציה באותה הנקודה (קרי: ה activation frame).

yield היא מילה שמורה חדשה בשפה - המשמשת את ה Generators. המשמעות שלה היא כמו return שגורם לשמירת ה state של ה iterator: כשנקרא ל ()next פעם נוספת - נמשיך בדיוק מהשורה הבאה אחרי זו שממנה יצאנו, ועם אותם המשתנים.

הנה דוגמה קצת יותר מציאותית:

  1. כשמגדירים generator מתוך מחלקה, אין צורך במילה function - נשארת רק הכוכבית (*).
  2. ה Generator בעצם מתאר רצף פעולות שנדרש כדי להשיג תוצאה כלשהי. במקרה שלנו, בכדי להגיע לפרטי התשלום של הלקוח:
    1. קודם צריך למצוא account token
    2. איתו לעשות login
    3. עם ה token הזה לשלוף את המידע על התשלומים.
כלומר ה generator מתאר את הקשר בין הפעולות והסדר שלהן - אבל הוא לא מבצע אותן. מי שיבצע אותן הוא מי שיקרא ל iterator, ויש פה הפרדה בין הגדרת סדר הפעולות (ה Generator) לביצוע שלהן (הפעלת ה iterator).

האם זה לא מעצבן לקרוא לפונקציה next, next, next בכדי שתסיים את הפעולה? למה לא לכתוב פונקציה רגילה שמבצעת ברצף את כל סדר הפעולות?

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

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

תכונה חשובה של generators היא יכולת מובנה להתערב בשלבים. אם אנחנו שולחים ערך לפונקציה ()next - הערך הזה יחליף את הערך ב state של האיטרטור:


סה"כ generators נחשבים פעולה מעט low-level עבור רוב המפתחים, ורבים שכן עבדו עם generators מצאו את עצמם עוטפים את התשובות ב Promises. מנגנון בשפה בשם Async / Await מסתמך על מנגנון ה generators בכדי לתת לנו רמת הפשטה גבוהה ונוחה מאוד לשימוש - לביצוע רצף פעולות אסינכרוני.



Async - Await


בפשט אפשר לומר ש Async-Await הוא Syntactic Sugar שהופך את השימוש ב Promises לקל ואלגנטי יותר.
עוד נוסחה שמוזכרת הרבה היא ״Async-Await ≈ Generators + Promises״ - אל דאגה! השימוש ב Generators הוא פרט מימוש מאחורי הקלעים.

Promises הם בהחלט שיפור יחסית לעבודה עם callbacks - אבל כשהשימוש בהם נעשה רחב, התגלה שגם להם יש כמה נקודות לא-נוחות.
Async Await הוא שיפור נוסף מעל Promises. מתכנתי ג׳אווהסקריפט ותיקים בוודאי קצת מקנאים במתכנתים חדשים שנוחתים לכל הנוחות הזו...


תחילה ארצה להציג את 2 הבעיות העיקריות שב Promises. אציין ש Async-Await גם יותר יעיל ברמת הביצוע וצריכת הזיכרון - במידה ואתם מריצים המון קוד מקבילי.


טיפול בכישלונות - בשני אופנים שונים:


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


Control Flow נוטה להסתבך - החלוקה לפונקציות הנשלחות ל then

בעבודה עם Promises קל להגיע לקוד מהסוג הבא (דוגמה מפושטת):


אולי אני צריך לבצע קריאה אסינכרונית ואולי לא - קשה לי לשלב את המשך ה flow בצורה אלגנטית בלי להוציא חלק מהקוד לפונקציה שניה / אולי לעטוף קטע קוד סינכרוני ב Promise בשביל הסימטריה...

הנה אותו הקוד עם async-await:


בהחלט קוד יותר אלגנטי, ושיותר קל לעבוד איתו!


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


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



את המילה השמורה await ניתן להציב רק בתוך פונקציה שהוגדרה כ Async.
הצבה שלה לפני Promise תגרום למפרשן להמתין עד של promise יש תשובה - ואז היא מחזירה אותה.

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

בעצם מאחורי הקלעים, המפרשן מייצר מפונקציית ה async מופע של Generator ומשתמש ב yield בכל נקודת await. ברגע שה Promise מוכן עם תשובה - הוא יקרא שוב ל Generator - שימשיך מהנקודה האחרונה.

זהו Syntactic Sugar לא קטן - ואין טעם לנסות לממש זאת לבד!


למרות ש await יוצר סדרתיות ברצת הפונקציה, עדיין אפשר להשתמש בכלים של promises על מנת להריץ קוד במקביל:


שימו לב שבדוגמה זו, לא עשינו כלום עם הערך שחזר מהביטוי "(...)await Promise.all". אם קרתה שם תקלה - לא נדע ממנה. אם היינו משתמשים ב return לערך - תקלה הייתה מתרגמת ל Exception.


לסיום הנושא, ולחידוד כיצד מתנהגת "ההפסקה" של await - בואו נחשוב מה יהיה פלט התוכנית הבאה:



התשובה היא:

a start
b start
a end
b end

אנו נכנסים ל a ואז ל b - ונעצרים בביצוע הפונקציה b בגלל ה await.
אנו יכולים להמשיך, ואכן ממשיכים בביצוע הפונקציה a ומדפיסים "a end". רק אז ה thread מתפנה לחכות ל await ולטפל בו - ואז לסיים את ביצוע הפונקציה b.

לו היינו מוסיפים ביטוי await גם על הפעלת הפונקציה b, התוצאה תהיה:

a start
b start
b end
a end

במקרה זה פונקציה a נערצת בקריאה "await b" והפונקציה b נעצרת קריאת ה await שלה.
רק לאחר שפונקציה b מסתיימת - ה await שבפונקציה a משתחרר - וממשיך לסיומה.

זה עשוי להיות מבלבל - אבל זו המציאות בעבודה עם Async-Await - שיש לשים לב אליה.


סיכום


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

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


שיהיה בהצלחה!