תזמון JS טוב יותר עם isInputPending()

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

Nate Schloss
Nate Schloss
Andrew Comminos
Andrew Comminos

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

כדי לבטל את הצורך הזה, Facebook הציעה ויישמה את ה-API isInputPending() ב-Chromium על מנת לשפר את הרספונסיביות בלי להסכים. על סמך המשוב לגבי גרסת המקור לניסיון, ביצענו כמה עדכונים ל-API, ואנחנו שמחים להודיע שה-API זמין עכשיו במשלוח כברירת מחדל ב-Chromium 87!

תאימות דפדפן

תמיכה בדפדפן

  • 87
  • 87
  • x
  • x

isInputPending() נשלח בדפדפנים מבוססי Chromium החל מגרסה 87. אף דפדפן אחר לא הצהיר על כוונה לשלוח את ה-API.

רקע

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

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

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

תרשים שמראה שכשמריצים משימות ארוכות של JS, לדפדפן יש פחות זמן לשליחת אירועים.

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

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

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

קיבלנו משוב מגרסת המקור לניסיון ומשאר החברים בקבוצת העבודה של W3C Web Performance, והטמענו שינויים ב-API.

דוגמה: מתזמן תפוקה

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

const DEADLINE = performance.now() + QUANTUM;
while (workQueue.length > 0) {
  if (performance.now() >= DEADLINE) {
    // Yield the event loop if we're out of time.
    setTimeout(processWorkQueue);
    return;
  }
  let job = workQueue.shift();
  job.execute();
}

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

זה בסדר, אבל איך אפשר להשתפר? בוודאי!

const DEADLINE = performance.now() + QUANTUM;
while (workQueue.length > 0) {
  if (navigator.scheduling.isInputPending() || performance.now() >= DEADLINE) {
    // Yield if we have to handle an input event, or we're out of time.
    setTimeout(processWorkQueue);
    return;
  }
  let job = workQueue.shift();
  job.execute();
}

הוספת שיחה ל-navigator.scheduling.isInputPending() מאפשרת לנו לענות לקלט מהר יותר, ועדיין להבטיח שהפעולה של חסימת התצוגה תפעל ללא הפרעות. אם אנחנו לא רוצים לטפל במשהו חוץ מקלט (למשל, ציור) עד לסיום העבודה, אפשר גם להאריך בקלות את האורך של QUANTUM.

כברירת מחדל, אירועים "רציפים" לא מוחזרים מ-isInputPending(). הפרמטרים האלה כוללים את mousemove, pointermove ועוד. אם אתם גם רוצים להפיק תועלת גם עבור אלה, אין בעיה. אחרי שמספקים אובייקט ל-isInputPending() עם הערך true של includeContinuous, אפשר להתחיל:

const DEADLINE = performance.now() + QUANTUM;
const options = { includeContinuous: true };
while (workQueue.length > 0) {
  if (navigator.scheduling.isInputPending(options) || performance.now() >= DEADLINE) {
    // Yield if we have to handle an input event (any of them!), or we're out of time.
    setTimeout(processWorkQueue);
    return;
  }
  let job = workQueue.shift();
  job.execute();
}

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

התפוקה לא תמיד רעה

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

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

חשוב לשים לב גם לדפים אחרים שחולקים לולאת אירוע. בפלטפורמות כמו Chrome ל-Android, בהרבה מקורות, הרבה מקורות יכולים לשתף לולאה של אירועים. הפונקציה isInputPending() אף פעם לא תחזיר את הערך true אם הקלט נשלח למסגרת בין מקורות, ולכן דפים עם רקע עלולים להפריע לתגובתיות של הדפים בחזית. כדאי להפחית, לדחות או להניב לעיתים קרובות יותר כשעובדים ברקע באמצעות Page Visibility API.

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

משוב

  • כותבים משוב על המפרט במאגר is-input-Pending.
  • אפשר לפנות אל @acomminos (אחד מכותבי המפרטים) ב-Twitter.

סיכום

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

תמונה ראשית (Hero) מאת Will H McMahan בתוכנית UnFlood.