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

Jake Archibald
Jake Archibald

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

אוקיי, זה היה די עמוס במונחים מקצועיים וקצת לא ברור. תכף נראה…

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

דפים מוסתרים

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

טיימרים ב-JavaScript

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

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

אם קוראים ל-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 ליצירת קול, אבל טראק אודיו שקט לא נחשב.

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

ויסות נתונים (throttle)

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

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

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

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

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

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

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

פתרונות חלופיים

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

סקרים במדינות

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

מספר דוגמאות:

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

Animation

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

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

אם אתם יכולים להצהיר על כל האנימציה מראש, כדאי להשתמש באנימציות CSS או ב-web animations API. ל-CSS יש את אותם יתרונות כמו ל-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 בגרסת Chrome 88 (ינואר 2021). בשלב הזה, התכונה מופעלת אצל 50% ממשתמשי Chrome Beta, ‏ Dev ו-Canary. כדי לבדוק את זה, צריך להשתמש בדגל הזה בשורת הפקודה כשמפעילים את Chrome Beta,‏ Dev או Canary:

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

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

העתיד

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

תמונת הכותרת של Heather Zabriskie ב-Unsplash.