ניפוי באגים ב-JavaScript אסינכרוני באמצעות כלי הפיתוח ל-Chrome

מבוא

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

למרבה המזל, עכשיו אפשר לראות ב-Chrome DevTools את מקבץ הקריאות המלא של קריאות חזרה (callbacks) אסינכרוניות של JavaScript.

סקירה כללית מהירה על סטאקים של קריאות אסינכרוניות.
סקירה כללית מהירה של סטאקים של קריאות אסינכרוניות. (בקרוב נסביר את התהליך של הדגמה הזו).

אחרי שתפעילו את התכונה 'מקבץ קריאות אסינכררוניות' בכלי הפיתוח, תוכלו להציג פירוט של מצב אפליקציית האינטרנט בנקודות זמן שונות. ניתוח מלא של נתיב הסטאק של חלק ממאזני האירועים, setInterval,‏ setTimeout,‏ XMLHttpRequest,‏ הבטחות (promises),‏ requestAnimationFrame,‏ MutationObservers ועוד.

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

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

הפעלת ניפוי באגים אסינכרוני ב-Chrome

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

לצד החלונית Call Stack בצד שמאל, מופיעה תיבת סימון חדשה בשם 'Async'. מפעילים או משביתים את ניפוי הבאגים האסינכרוני באמצעות תיבת הסימון. (עם זאת, אחרי שמפעילים אותו, לא כדאי להשבית אותו לעולם).

מפעילים או משביתים את התכונה האסינכרונית.

תיעוד של אירועי טיימר עם עיכוב ותגובות XHR

סביר להניח שראית את ההודעה הזו בעבר ב-Gmail:

Gmail מנסה שוב לשלוח את האימייל.

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

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

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

אם תסתכלו רק בחלונית Call Stack בגרסאות קודמות של DevTools, נקודת העצירה ב-postOnFail() תספק לכם מעט מידע על המקור שממנו postOnFail() נקרא. אבל מה ההבדל כשמפעילים את סטאקים האסינכרוניים:

לפני
נקודת עצירה מוגדרת בדוגמה לדמיית Gmail ללא סטאקים של קריאות אסינכרוניות.
חלונית Call Stack בלי הפעלה אסינכרונית.

כאן אפשר לראות ש-postOnFail() הופעל מקריאה חוזרת (callback) של AJAX, אבל אין מידע נוסף.

אחרי
נקודת עצירה מוגדרת בדוגמה לדמיית Gmail עם סטאקים של קריאות אסינכרוניות.
החלונית Call Stack עם הפעלה אסינכרונית.

כאן אפשר לראות שה-XHR הופעל מ-submitHandler(). איזה יופי!

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

submitHandler()
נקודת עצירה מוגדרת בדוגמה לדומיין Gmail עם סטאקים של קריאות אסינכרוניות
retrySubmit()
נקודת עצירה נוספת שמוגדרת בדוגמה המדומה של Gmail עם סטאקים של קריאות אסינכרוניות

מעקב אחר ביטויים באופן אסינכרוני

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

דוגמה לשימוש בביטויי מעקב עם סטאקים של קריאות אסינכרוניות

הערכת קוד מתחומים קודמים

בנוסף למעקב אחרי ביטויים, אפשר לבצע פעולות בקוד מתחומים קודמים ישירות בחלונית מסוף JavaScript של DevTools.

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

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

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

פתרון של הבטחות שרשורו

אם חשבתם שהתהליך הקודם של Gmail היה קשה לפענוח בלי הפעלת התכונה של סטאק הקריאות האסינכרוניות, תוכלו לדמיין כמה קשה יהיה לפתור תהליכים אסינכרונים מורכבים יותר, כמו הבטחות שרשומות בשרשור? נבחן שוב את הדוגמה האחרונה במדריך של Jake Archibald בנושא Promises ב-JavaScript.

הנה אנימציה קצרה של הליכה בסטאקים של הקריאות בדוגמה של Jake‏ async-best-example.html.

לפני
נקודת עצירה מוגדרת בדוגמה של promises ללא סטאקים של קריאות אסינכרניות
חלונית Call Stack בלי הפעלה אסינכרונית.

שימו לב למידע הקצר יחסית שמוצג בחלונית Call Stack כשמנסים לנפות באגים ב-promises.

אחרי
נקודת עצירה מוגדרת בדוגמה של promises עם סטאקים של קריאות אסינכרוניות.
החלונית Call Stack עם הפעלה אסינכרונית.

וואו! הבטחות כאלה. הרבה קריאות חזרה.

קבלת תובנות לגבי אנימציות באתר

נמשיך להתעמק בארכיונים של HTML5Rocks. זוכרים את המאמר של פול לואיס בנושא אנימציות יעילות, מהירות וחזקות יותר באמצעות requestAnimationFrame?

פותחים את הדמו של requestAnimationFrame ומוסיפים נקודת עצירה בתחילת השיטה update()‎ (בסביבות שורה 874) של post.html. בעזרת סטאקים של קריאות אסינכררוניות אנחנו מקבלים הרבה יותר תובנות לגבי requestAnimationFrame, כולל היכולת לחזור כל הדרך אל פונקציית ה-callback של אירוע הגלילה שהתחיל את התהליך.

לפני
נקודת עצירה מוגדרת בדוגמה של requestAnimationFrame ללא סטאקים של קריאות אסינכרוניות.
חלונית Call Stack בלי הפעלה אסינכרונית.
אחרי
נקודת עצירה מוגדרת בדוגמה של requestAnimationFrame עם סטאקים של קריאות אסינכרוניות
ועם הפעלת אסינכרוניזציה.

מעקב אחר עדכוני DOM כשמשתמשים ב-MutationObserver

MutationObserver מאפשרים לנו לזהות שינויים ב-DOM. בדוגמה הפשוטה הזו, כשלוחצים על הלחצן, צומת DOM חדש מצורף ל-<div class="rows"></div>.

מוסיפים נקודת עצירה ב-nodeAdded() (שורה 31) בקובץ demo.html. כשמפעילים את סטאקים של קריאות אסינכררוניות, אפשר עכשיו לעבור בסטאק הקריאות חזרה דרך addNode() לאירוע הקליק הראשוני.

לפני
נקודת עצירה מוגדרת בדוגמה של mutationObserver ללא סטאקים של קריאות אסינכרוניות.
חלונית Call Stack בלי הפעלה אסינכרונית.
אחרי
נקודת עצירה מוגדרת בדוגמה של mutationObserver עם סטאקים של קריאות אסינכרוניות.
ועם הפעלת אסינכרוניזציה.

טיפים לניפוי באגים ב-JavaScript בסטאקים של קריאות אסינכרוניות

מתן שמות לפונקציות

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

לדוגמה, ניקח פונקציה אנונימית כזו:

window.addEventListener('load', function() {
  // do something
});

נותנים לו שם כמו windowLoaded():

window.addEventListener('load', function <strong>windowLoaded</strong>(){
  // do something
});

כשאירוע הטעינה יופעל, הוא יופיע ב-DevTools בתרשים סטאק עם שם הפונקציה במקום עם הכיתוב המסתורי (anonymous function). כך קל יותר לראות במבט מהיר מה קורה ב-stack trace.

לפני
פונקציה אנונימית.
אחרי
פונקציה בעלת שם

המשך למידה

לסיכום, אלה כל פונקציות ה-callbacks האסינכרוניות שבהן סטאק הקריאות המלא יוצג בכלי הפיתוח:

  • טיימרים: חוזרים למקום שבו setTimeout() או setInterval() הופעלו.
  • בקשות XHR: חוזרים אחורה למקום שבו xhr.send() נקרא.
  • מסגרות אנימציה: חוזרים אחורה למקום שבו requestAnimationFrame נקרא.
  • Promises: אפשר לחזור אחורה למקום שבו התבצעה התאמת נתונים ל-Promise.
  • Object.observe: חוזרים למקום שבו פונקציית ה-callback של הצופה אוחדה במקור.
  • MutationObservers: חוזרים אחורה למקום שבו הופעל האירוע של Mutation Observer.
  • window.postMessage(): ניתוח של קריאות להעברת הודעות בתוך תהליך.
  • DataTransferItem.getAsString()
  • FileSystem API
  • IndexedDB
  • WebSQL
  • אירועי DOM שעומדים בדרישות דרך addEventListener(): עוברים חזרה למקום שבו האירוע הופעל. מסיבות שקשורות לביצועים, לא כל אירועי ה-DOM עומדים בדרישות לשימוש בתכונה 'מחסומי קריאות אסינכרוני'. דוגמאות לאירועים שזמינים כרגע: 'scroll',‏ 'hashchange' ו-'selectionchange'.
  • אירועי מולטימדיה דרך addEventListener(): חוזרים למקום שבו האירוע הופעל. אירועי המולטימדיה הזמינים כוללים: אירועי אודיו ווידאו (למשל 'play',‏ 'pause',‏ 'ratechange'), אירועי WebRTC MediaStreamTrackList (למשל 'addtrack',‏ 'removetrack') ואירועי MediaSource (למשל 'sourceopen').

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

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