ויסות נתונים (throttling) כבד של טיימרים בשרשרת של JS החל מגרסה Chrome 88

Jake Archibald
Jake Archibald

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

אוקיי, השיחה הייתה עמוסה מדי וקצת לא ברורה. שנתחיל?

הסברים על המונחים

דפים מוסתרים

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

טיימרים ב-JavaScript

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

טיימרים משורשרים

אם קוראים לפונקציה setTimeout באותה משימה של קריאה חוזרת (callback) של setTimeout, ההפעלה השנייה תהיה 'משורשר'. עם setInterval, כל איטרציה היא חלק מהשרשרת. יכול להיות שיהיה קל יותר להבין את זה בעזרת הקוד:

let chainCount = 0;

setInterval(() => {
  chainCount++;
  console.log(`This is number ${chainCount} in the chain`);
}, 500);

וגם:

let chainCount = 0;

function setTimeoutChain() {
  setTimeout(() => {
    chainCount++;
    console.log(`This is number ${chainCount} in the chain`);
    setTimeoutChain();
  }, 500);
}

איך פועל ויסות הנתונים

ויסות הנתונים קורה בשלבים:

ויסות נתונים מינימלי

המצב הזה קורה לטיימרים שמתוזמנים כאשר מתקיים אחד או יותר מהתנאים הבאים:

  • הדף גלוי.
  • השמיעו קולות מהדף ב-30 השניות האחרונות. הוא יכול להיות מכל אחד מממשקי ה-API ליצירת סאונד, אבל טראק שקט של אודיו לא נחשב.

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

ויסות נתונים

המצב הזה קורה בטיימרים שמתוזמנים כשויסות הנתונים המינימלי לא חל, וכל אחד מהתנאים הבאים מתקיים:

  • מספר השרשרת קטן מ-5.
  • הדף מוסתר למשך פחות מ-5 דקות.
  • WebRTC בשימוש. באופן ספציפי, יש RTCPeerConnection עם RTCDataChannel 'פתוח' או MediaStreamTrack 'פעיל'.

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

ויסות נתונים אינטנסיבי

טוב, הנה הקטע החדש ב-Chrome 88. ויסות נתונים אינטנסיבי קורה לטיימרים שמתזמנים כשלא מתקיימים התנאים של ויסות נתונים מינימלי או ויסות נתונים, וכל התנאים הבאים מתקיימים:

  • הדף מוסתר למשך יותר מ-5 דקות.
  • מספר השרשרת הוא 5 ומעלה.
  • הדף היה שקט במשך 30 שניות לפחות.
  • WebRTC לא בשימוש.

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

דרכים לעקיפת הבעיה

בדרך כלל יש חלופה טובה יותר לטיימר, או שאפשר לשלב טיימרים עם משהו אחר כדי לייעל את המעבדים (CPU) ואת חיי הסוללה.

סקרים במדינה

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

דוגמאות:

קיימים גם טריגרים של התראות, אם רוצים להציג התראה בזמן מסוים.

Animation

אנימציה היא פעולה ויזואלית, ולכן לא צריך לצרוך את זמן המעבד (CPU) כשהדף מוסתר.

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

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

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

function animationInterval(ms, signal, callback) {
  const start = document.timeline.currentTime;

  function frame(time) {
    if (signal.aborted) return;
    callback(time);
    scheduleFrame(time);
  }

  function scheduleFrame(time) {
    const elapsed = time - start;
    const roundedElapsed = Math.round(elapsed / ms) * ms;
    const targetNext = start + roundedElapsed + ms;
    const delay = targetNext - performance.now();
    setTimeout(() => requestAnimationFrame(frame), delay);
  }

  scheduleFrame(start);
}

שימוש:

const controller = new AbortController();

// Create an animation callback every second:
animationInterval(1000, controller.signal, time => {
  console.log('tick!', time);
});

// And stop it:
controller.abort();

בדיקה

השינוי הזה יופעל אצל כל משתמשי Chrome בגרסה 88 של Chrome (ינואר 2021). נכון לעכשיו, היא מופעלת ל-50% מהמשתמשים ב-Chrome בטא, ב-Dev ובגרסה הזאת של Canary. אם רוצים לבדוק אותו, אפשר להשתמש בסימון הזה בשורת הפקודה כשמפעילים את Chrome בטא, Dev או Canary:

--enable-features="IntensiveWakeUpThrottling:grace_period_seconds/10,OptOutZeroTimeoutTimersFromThrottling,AllowAggressiveThrottlingWithWebSocket"

הארגומנט grace_period_seconds/10 גורם לויסות נתונים אינטנסיבי אחרי 10 שניות מהדף מוסתר, במקום 5 הדקות המלאות, וכך קל יותר לראות את ההשפעה של ויסות הנתונים.

העתיד

מאחר שטיימרים הם מקור לשימוש מופרז במעבד (CPU), נמשיך לבחון דרכים לויסות נתונים בלי לשבור את תוכן האינטרנט, וממשקי API שנוכל להוסיף או לשנות בהתאם לתרחישי שימוש. באופן אישי, הייתי רוצה לבטל את הצורך ב-animationInterval לטובת קריאות חוזרות (callback) ביעילות של אנימציה בתדירות נמוכה. אם יש לכם שאלות, תוכלו לפנות אליי ב-Twitter.

תמונת כותרת מאת Heather Zabriskie ב-UnFlood.