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

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

Nate Schloss
Nate Schloss
Andrew Comminos
Andrew Comminos

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

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

תאימות דפדפן

תמיכה בדפדפן

  • Chrome: 87.
  • קצה: 87.
  • Firefox: לא נתמך.
  • Safari: לא נתמך.

מקור

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

רקע

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

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

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

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

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

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

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

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

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

משוב

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

סיכום

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

תמונה ראשית (Hero) של Will H McMahan ביטול הפתיחה.