ניתוח פעולות אסינכרוניות בתוכניות JavaScript

ניתוח פעולות אסינכרוניות בתוכניות JavaScript
Chart vector created by stories - www.freepik.com

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

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

לדוגמא, נסתכל על פקודות סינכרוניות. נבצע מספר פקודות console.log במקביל:

console.log('1')
console.log('2')
console.log('3')

נקבל כפלט לפי הסדר שהתוכנית רשומה '1','2' ו'3'. כלומר, כמצופה, שורה אחר שורה.

בעוד שכל תהליך שכזה נמצא בהרצה או עיבוד, שום דבר אחר לא יכול להתרחש – יתר העיבוד מושהה. זה מכיוון ש-JavaScript היא single threaded (נפרט על כך בהמשך). רק דבר אחד יכול להתרחש בכל פעם על ה-single main thread, וכל היתר חסום לביצוע או לעיבוד עד אשר אותה פעולה תושלם. גישה זו שונה מהרבה שפות פופלאריות אחרות, ולכן דיי קשה לכתוב קוד אסינכרוני בתוכניות JavaScript.

רשומה זו נכתבה בהשראה של הסמינר שביצעתי במהלך הלימודים בתואר על תוצאות המאמר "Static Analysis for Asynchronous JavaScript Programs" של Thodoris Sotiropoulos ו-Benjamin Livshits הנותנים גישה מעניינת לכיצד ניתן לבצע ניתוח סטטי של תוכנית JavaScript אסינכרונית. ניתוח זה מאפשר לזהות שגיאות פוטנציאליות ולהעריך ביצועים של תוכניות.

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

תוכן עניינים

  1. Event Loop
    1. פעולות אסינכרוניות
    2. קולבקים
    3. הבטחות (Promises)
  2. הגדרת גרף קריאות
    1. דיוק האנליזה
    2. התייחסות לקולבקים (Callback)
    3. התייחסות להקשר (Context)
  3. הערכה ניסויית
    1. סימולציה
    2. ניסוי בסביבה אמיתית
    3. מסקנות הניסויים
  4. סיכום

Event Loop

JavaScript היא שפה עם הליך משנה יחיד (Single Thread), לפיכך פותח עבור השפה מודל מקביליות בשם ה-Event Loop האחראי להרצת הקוד, איסוף ועיבוד אירועים (Events) והרצת תור של תתי-משימות. מודל זה שונה מהאופן בו רצות שפות אחרות לדוגמא C וJava.

את קונספט המודל אפשר להרכיב באמצעות 3 מבני נתונים, (1) מחסנית, השומרת בתוכה מסגרות (Frames), אשר כל מסגרת מייצגת קריאה של פונקציה והסביבה (ההקשר, נפרט על על זה יותר בהמשך) שלה. (2) ערמה של אובייקטים בזיכרון, לאו דווקא מסודרים. (3) תור אירועים (Event queue), המכיל הודעה על אירועים המקושרת לפונקציה המטפלות באירוע. בשלב מסויים בריצת התוכנית (יפורט יותר בהמשך) התוכנית תכניס את הפונקציה המקושרת להודעה הישנה ביותר בתור (First in first out) למחסנית ותטפל בה.

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

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

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

ישנם שלושה דרכים להוסיף הודעות לתור [1]:

  1. טיימרים לפעולות, לדוגמא setTimeout מקבל 2 ארגומנטים, הראשון זוהי הפעולה שיש לבצע וארגומנט שני זה הזמן המינימלי שיש לעקב את ביצוע הפעולה. כאשר הזמן אשר הוגדר עובר, ההודעה נוספת לתור וכאשר יגיע תורה היא תתבצעה. אם המחסנית והתור ריקים, הפונקציה תרוץ באופן מיידי אך במידה ולא ההודעה תצטרך לחכות לתורה לצורך טיפול. ולכן הזמן המוגדר הינו המינימלי לריצתה ואי אפשר להתחייב מתי היא תרוץ בדיוק.
  2. פעולות אסינכרוניות, כאשר ישנה פעולה כזו, לדוגמא ביצוע קריאת HTTP, תירשם ההודעה לתור וכאשר נקבל תשובה לקריאה ההודעה תירשם לתור ותטופל כשיגיע תורה.
  3. אירועים(Events), סוג פעולה זה מאפשר להירשם לאירועים, לדוגמא לחיצת מקש מקלדת, ולהריץ את הפונקציה שרשמנו לאירוע כאשר האירוע יתקיים.

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

פעולות אסינכרוניות

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

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

const response = fetch('myImage.png');

const blob = response.blob();

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

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

קולבקים

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

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

btn.addEventListener('keydown', () => {
    console.log('you pushed a key')
})

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

כאשר אנחנו מעבירים callback function כפרמטר לפונקציה אחרת, אנחנו רק מעבירים מצביע לפונקציה כפרמטר, כלומר ה-callback function לא מופעל באופן מיידי. הוא נקרא לאחר מכן (“called back”) באופן אסינכרוני איפשהו בתוך הגוף של הפונקציה שקיבלה אותו כפרמטר. הפונקציה שקיבלה אותו כפרמטר היא האחראית להפעיל את ה-callback function כשנצטרך.

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

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

הבטחות (Promises)

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

להבטחה יש שלושה מצבים:

  1. Pending – מצב התחלתי, לא מומש ולא נכשל.
  2. Fulfilled – מצב האומר שהפעולה צלחה וההבטחה מומשה.
  3. Rejected – הפעולה נכשלה.

הבטחה במצב Pending יכולה להיות מומשת (Fulfilled) ולהעביר ערך אלה, או להיכשל (Rejected) ולהעביר הודעת שגיאה. ניתן לרשום להבטחה קולבק לטיפול כאשר יתבצע שינוי לכל אחד מהמצבים הנ"ל באמצעות .then() המחזיר הבטחה גם כן, מה שמאפשר לשרשר מספר הבטחות (Promise chain).

בדרך דומה לtry…catch הסינכרוני, ניתן להגדיר להבטחה קולבק .catch אשר ירוץ בסוף אם אחד מהבלוקים של .then() יכשל. אובייקט שגיאה – error object – ייהפך לזמין בתוך הcatch() הוא יוכל לשמש עבור דיווח על סוג השגיאה שהתרחשה.

הבטחות הן בעצם הסגנון החדש לקוד אסינכרוני הנורא פופולרי ב-Web APIs מודרניים. דוגמא טובה לכך היא fetch() API, אשר הוא בעצם כמו גרסה מודרנית ויעילה יותר של XMLHttpRequest.

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

let myFirstPromise = new Promise((resolve, reject) => {
    setTimeout(function(){
      resolve("Success!")
    }, 250)
})

myFirstPromise.then((successMessage) => {
    console.log("Yay! " + successMessage)
})

בדוגמא אנו יוצרים הבטחה בה אנו מבצעים פעולה אסינכרונית, מגדירים שההבטחה תמומש עם הערך “success!” אחרי 250 מילישניות. לאחר מכן אנו מגדירים קולבאק שיטפל בהבטחה במקרה והיא תמומש (reslove היא חלק מהPromise API שבדומה ל-return הקלאסי של פונקציה מחזיר ערך ומודיע להבטחה שהפעולה התממשה בהצלחה) ותדפיס "Yay! " משורשר עם הערך "Success!".

הבטחות מול קולבקים

הבטחות וקולבאקים משלימים אחד את השני. הבטחה מאפשרת לקשור מספר פעולות אסינכרוניות על ידי יצירת שרשרת של קולבקים המסתמכים בכל פעם על התממשות של הקולבאק קודם ברשימה. פעולה זאת אומנם נתמכה גם לפני שהוצג הPromises לשפה JavaScript אבל הייתה מסורבל ומורכבת והייתה גורמת לבעיה הנקראת "Pyramid of doom".

step1(args, function() {
    step2(args, function() {
        step3(args, function() {
            step4(args, function() {
                step5(args, function() {
                    step6(args, function() {
                        ... 
                    });
                });
            });
        });
    });
});

(דוגמא לPyramid of Doom)

הגדרת הגרף קריאות

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

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

דיוק האנליזה

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

התייחסות לקולבקים (Callback)

גישה נאיבית ליצירת גרף קריאות, control flow graph, היא חיבור צמתים על ידי הצגת מי קורא למי. אך ב- JavaScriptפונקציות אסניכרוניות מתוזמנות ונקראות על ידי ה-Event Loop, דבר הגורם לניתוח לאבד הקשר ולרשום את הקריאות תחת אותו גוף, בגלל שהן ניקראות על ידי אותו המקום בתוכנית (הEvent loop).

על מנת לפתור את העניין במאמר מציגים גישה להפריד בין הקריאות באמצעות מעקב אחר החילחול של המידע בשרשת הקריאות. במקום לייחס את הקריאה למי שקורא לה (כפי שציינתי קודם זה הEvent Loop) נייחס את הקריאה לפונקציה שקראה לה ולפונקציה שמצפה לקבל את התשובה (Response) שלה.

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

function foo() { ... }

var x = Promise.resolve()
     .then(foo)
     .then(function ff1() { ... })
     .then(foo)
     .then(function ff2() { ... })
     .then(foo)
     .then(function ff3() { ... })

(ציור 1)

עץ קריאות עם יחוס להקשר בין הקריאות יראה כך:

(ציור 2)

התייחסות להקשר (Context)

כפי שציינתי, אחד הפרמטרים החשובים בבניית הגרף זה יחוס החשיבות להקשר בו נקראה הפונקציה, המאפשר דיוק רב יותר בניתוח התוכנית. קיימים שיטות שונות להגדרת ההקשר בין פונקציות אסינכרוניות, לדוגמא object-sensitivity [2,3] המפריד בין קריאות שונות בהתאם למי שמבצע את הקריאה, שיטה זה הוכיחה את עצמה כמועילה עבור שפות שהן מובנות-עצמים, אך שימוש בהקשר זה איננו מתאים לעבודה עם קריאות אסינכרוניות מכיוון שרוב הקריאות יתבצעו על ידי אובייקטים גלובלים. שיטה נוספת שהשתמשו במחקרים אחרים על JavaScript [4,5] היא בניית עץ לפי הארגומנטים של הפונקציות אך היא לא יעילה במיוחד כאשר ישנם פונקציות שונות עם אותן פרמטרים.

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

ניתן לראות זאת בציור 1, המייצג תור של קריאות בהתאם לסדר בו הן יקראו. תוכנית זו יוצרת שרשרת Promises שבה אנו רושמים קריאות חוזרות שונות בכל שלב של חישוב אסינכרוני. בשורה 1, אנו מגדירים את הפונקציה foo(). אנו קוראים באופן אסינכרוני foo() מספר פעמים, בשורות 4, 6 ו-8. שרשראות של Promises מאפשרות לנו לאכוף ביצוע דטרמיניסטי של קריאות חוזרות מתאימות.

במידה והיינו מסתכלים על הגרף מבלי להתייחס להקשר הגרף היה ניראה כך:

בצורת ניתוח זו אין משמעות לקריאות השונות של foo() ולכן הקריאה נירשמת פעם אחת בלבד, כתוצאה מכך נאבד את היחס בין ff1(), ff2() לבין foo() בניתוח התוכנית מכיוון שהפונקציה ניקראת לפני ואחרי עבור 2 הפונקציות הנ"ל. ניתוח של התוכנית באופן הזה יתן תוצאות פחות מדוייקות.

הערכה ניסויית

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

  • סימולציה מעמיקה של 29 קטעי קוד קצרים של כ-20 עד 50 שורות עם מימושים שונים של פונקציות.
  • ניתוח 6 תוכניות אמיתיות פופלאריות שנלקחו מGitHub.

סימולציה

במסגרת המאמר, בוצעה סימולציה של יצירת עץ קריאות ל-29 קטעי קוד שונים, כאשר עבור כל קטע קוד כזה נבדקו בארבעה אופנים שונים:

  1. ניתוח ללא התייחסות להקשר וקולבאקים (NC-No)
  2. התייחסות להקשר אבל לא לקולבאקים (NC-QR)
  3. התייחסות לקולבאקים אבל לא להקשר (C-No)
  4. התייחסות גם לקולבאקים וגם להקשר (C-QR)

בכולם בנוסף ישנה התייחסות לobject-sensitivity המפריד בין קריאות שונות בהתאם למי שמבצע את הקריאה. עבור כל גישה נבדק גם מספר הType Errors הקיימים בתוכנית, זוהי מטריקה שפותחה על ידי Vineeth Kashyap [5] הסופרת את מספר השגיאות הפוטנציאליות, ככל שהמספר קטן יותר כך המערכת צפויה להוציא שגיאה קטן יותר. כל תכנית רצה 10 פעמים עבור כל אחד מהאופנים על מנת לקבל תשובה אמינה, כמו כן כל התוכניות רצו על אותה המכונה.

רמת הדיוק של הגרף קריאות נקבעת על ידי חילוק מספר הקריאות המקושרות בגרף כהלכה במספר הקריאות המקושרות בפועל הקיימות בתוכנית.

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


(טבלה 1)

ניסוי בסביבה אמיתית

עבור ביצוע הניסוי בסביבה אמיתית נבחרו 6 פרוייקטים פופלאריים מהאתר GitHub. אופן בחירת התוכניות נעשה בשני שלבים, שלב ראשון בחירת התוכניות שהכי הרבה משתמשים מתקינים בעזרת מנהל החבילות NPM(Node Package Manager) וסינון באמצעות הAPI של GitHub של איזה מהתוכניות מתוייג כך שהן קשורות לPromises.

התוכניות שנבחרו הן: controlled-promise, fetch, honoka, axios, pixiv-client, node-golb אשר באופן לא מפתיע כל התוכניות עוזרות לבצע פעולות אסינכרוניות לדוגמא קראית קבצים, וביצוע בקשות HTTP.

תוכניות אלה נבדקו באותן תנאים ונבדקו אותם הפרמטרים כמו בסימולציה.

תוצאות הניתוח עבור תוכניות אלה שהוצגו במאמר:


(טבלה 2)

בנוסף נבדקו זמני הריצה של האנליזות לתוכניות:


(טבלה 3)

מסקנות הניסויים

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

עבור הספריות אשר נמצאות בשימוש בעולם האמיתי ניתן גם כן לראות (טבלה 2) שכאשר יש יחוס להקשר רמת הדיוק משתפרת בין 4.6% ל26.9%. מצד שני, כאשר יש יחוס לקולבקים ב5 מתוך 6 תוכניות דווח על ירידה של 13.9% בType Errors. כאשר אנו מתייחסים גם ליחס ההקשר וליחס הקולבקים ניתן להעלות את רמת הדיוק אף יותר, ניתן לראות לדוגמא את סיפריית Axios. רמת הדיוק הממוצעות של כל התוכניות עלתה ב28.5% והגיע לרמת דיוק של 88% סה"כ. וזאת בעוד בגישה הנאבית (ללא התייחסות להקשר ולקולבקים) אחוז הדיוק עומד רק על 79%.

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

בטבלה 3, אנו נחשפים לזמני הריצה של התוכניות, תובנה מעניינת מראה קורולציה בין הדיוק של גרף הקריאות וזמן הריצה, ככל שהדיוק עולה זמן הריצה יורד, בטווח של בין 3% ל18%, לדוגמא ניתן לראות את סיפריית Fetch, תוצאה זאת הינה עקבית ביחד עם אנליזות דומות שנעשו [6].

מנגד ניתן לראות קפיצה לא טריווילאית כלל של בין 12% ל30.6% בזמן ביצוע הניתוח עבור הספריה pixiv-client.

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

סיכום

במבנה הבסיסי שלה, JavaScript היא סינכרונית, שפה single-threaded, כך רק פעולה אחת יכולה להיות מעובדת בכל זמן נתון. יחד עם זאת, הדפדפנים הגדירו פונקציות ו-APIs שמאפשרים לנו לרשום פונקציות שלא ירוצו באופן סינכרוני, ובמקום זאת, יופעלו באופן אסינכרוני כאשר אירוע מסויים מתרחש. זה אומר שאנחנו יכולים לתת לקוד שלנו לעשות דברים אחרים באותו הזמן, מבלי לחסום או לעצור את ה-main thread. בין אם אנחנו רוצים להירץ קוד באופן סינכרוני או אסינכרוני, זה תלוי במה שאנחנו מנסים לעשות.

בעבודה זו הגדרנו תחילה מה היא המשמעות של תוכנית JavaScript אסינכרונית, מגבלות השפה וכיצד היא מתמודדת איתם, ואילו בעיות יכולות לצוץ במהלך העבודה. בנוסף הראנו מדוע מיפוי control flow graph אשר בדרך כלל מתאים להרבה שפות תכנות אחרות איננו מתאים במקרה הזה. דבר המקשה על ניתוח סטאטי של תוכניות.

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

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

ביבליוגרפיה

  1. https://www.britannica.com/technology/JavaScript
  2. Ondřej Lhoták and Laurie Hendren. Evaluating the Benefits of Context-sensitive Points-to Analysis Using a BDD-based Implementation. ACM Trans. Softw. Eng. Methodol., 18(1):3:1– 3:53, 2008. doi:10.1145/1391984.1391987.
  3. Ana Milanova, Atanas Rountev, and Barbara G. Ryder. Parameterized Object Sensitivity for Points-to Analysis for Java. ACM Trans. Softw. Eng. Methodol., 14(1):1–41, 2005. doi: 10.1145/1044834.1044835
  4. Simon Holm Jensen, Anders Møller, and Peter Thiemann. Type Analysis for JavaScript. In Proceedings of the 16th International Symposium on Static Analysis, SAS ’09, pages 238–255, 2009. doi:10.1007/978-3-642-03237-0_17
  5. Vineeth Kashyap, Kyle Dewey, Ethan A. Kuefner, John Wagner, Kevin Gibbons, John Sarracino, Ben Wiedermann, and Ben Hardekopf. JSAI: A static analysis platform for JavaScript. In Proceedings of the 22Nd ACM SIGSOFT International Symposium on Foundations of Software Engineering, FSE 2014, pages 121–132, 2014. doi:10.1145/2635868.2635904.
  6. C. Park, S. Won, J. Jin, and S. Ryu. Static Analysis of JavaScript Web Applications in the Wild via Practical DOM Modeling (T). In 2015 30th IEEE/ACM International Conference on Automated Software Engineering (ASE), pages 552–562, 2015. doi:10.1109/ASE.2015.27.

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *

הבא בתור:

לבנות מנהל קיצורי מקלדת בעזרת שלושה מבני נתונים

לבנות מנהל קיצורי מקלדת בעזרת שלושה מבני נתונים