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

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

Nate Schloss
Nate Schloss
Andrew Comminos
Andrew Comminos

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

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

תאימות דפדפן

תמיכה בדפדפנים

  • Chrome: 87.
  • Edge:‏ 87.
  • Firefox: לא נתמך.
  • Safari: לא נתמך.

מקור

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

רקע

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

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

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

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

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

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

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

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

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

נניח שאתם צריכים לבצע כמה פעולות לחסימת התצוגה כדי לטעון את הדף, למשל יצירת רכיבי עיצוב מרכיבים, חיסור של ראשוניים או פשוט ציור של גלגל טעינה מגניב. כל אחד מהם מחולק לפריט עבודה נפרד. בעזרת התבנית של מתזמן המשימות, נציג סקיצה של אופן העיבוד של העבודה בפונקציה היפותטית 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() עם הערך includeContinuous שמוגדר כ-true, וזהו:

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() מאחורי הקלעים ללא שכתובים משמעותיים.

כניעה לא תמיד היא דבר רע

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

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

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

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

משוב

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

סיכום

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

התמונה הראשית (Hero) היא של Will H McMahan ב-Unsplash.