Zware beperking van gekoppelde JS-timers vanaf Chrome 88

Chrome 88 (januari 2021) zal onder bepaalde omstandigheden gekoppelde JavaScript-timers voor verborgen pagina's zwaar beperken. Hierdoor wordt het CPU-gebruik verminderd, waardoor ook het batterijgebruik wordt verminderd. Er zijn enkele randgevallen waarin dit het gedrag zal veranderen, maar timers worden vaak gebruikt wanneer een andere API efficiënter en betrouwbaarder zou zijn.

Oké, dat was behoorlijk veel jargon en een beetje dubbelzinnig. Laten we erin graven...

Terminologie

Verborgen pagina's

Over het algemeen betekent verborgen dat een ander tabblad actief is, of dat het venster is geminimaliseerd, maar browsers kunnen een pagina als verborgen beschouwen wanneer de inhoud ervan totaal niet zichtbaar is. Sommige browsers gaan hier verder dan andere, maar u kunt altijd de API voor paginazichtbaarheid gebruiken om bij te houden wanneer de browser denkt dat de zichtbaarheid is veranderd.

JavaScript-timers

Met JavaScript-timers bedoel ik setTimeout en setInterval , waarmee u ergens in de toekomst een terugbelactie kunt plannen. Timers zijn nuttig en verdwijnen niet, maar soms worden ze gebruikt om na te gaan of een gebeurtenis efficiënter en nauwkeuriger zou zijn.

Geketende timers

Als u setTimeout aanroept in dezelfde taak als een setTimeout -callback, wordt de tweede aanroep 'geketend'. Met setInterval maakt elke iteratie deel uit van de keten . Dit is misschien gemakkelijker te begrijpen met code:

let chainCount = 0;

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

En:

let chainCount = 0;

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

Hoe de throttling werkt

Het smoren gebeurt in fasen:

Minimale throttling

Dit gebeurt met timers die zijn gepland wanneer een van de volgende situaties van toepassing is:

  • De pagina is zichtbaar .
  • De pagina heeft de afgelopen 30 seconden geluiden gemaakt. Dit kan afkomstig zijn van een van de geluidsproducerende API's, maar een stille audiotrack telt niet.

De timer wordt niet beperkt, tenzij de gevraagde time-out minder dan 4 ms bedraagt ​​en het aantal ketens 5 of meer is. In dat geval wordt de time-out ingesteld op 4 ms. Dit is niet nieuw; browsers doen dit al jaren.

Versnelling

Dit gebeurt met timers die zijn gepland wanneer minimale beperking niet van toepassing is en een van de volgende situaties van toepassing is:

  • Het aantal ketens is minder dan 5.
  • De pagina is minder dan 5 minuten verborgen geweest.
  • WebRTC is in gebruik. Concreet is er een RTCPeerConnection met een 'open' RTCDataChannel of een 'live' MediaStreamTrack .

De browser controleert de timers in deze groep één keer per seconde . Omdat ze slechts één keer per seconde worden gecontroleerd, worden timers met een vergelijkbare time-out samengevoegd, waardoor de tijd die het tabblad nodig heeft om code uit te voeren wordt geconsolideerd. Dit is ook niet nieuw; browsers doen dit tot op zekere hoogte al jaren.

Intensieve throttling

Oké, hier is het nieuwe deel in Chrome 88. Intensieve beperking vindt plaats met timers die zijn gepland wanneer geen van de minimale beperking of beperkingsvoorwaarden van toepassing is, en aan alle volgende voorwaarden wordt voldaan:

  • De pagina is langer dan 5 minuten verborgen geweest.
  • Het aantal ketens is 5 of hoger.
  • De pagina is minimaal 30 seconden stil geweest.
  • WebRTC is niet in gebruik.

In dit geval controleert de browser de timers in deze groep één keer per minuut . Net als voorheen betekent dit dat timers samenkomen in deze controles van minuut tot minuut.

Oplossingen

Er is meestal een beter alternatief voor een timer, of timers kunnen worden gecombineerd met iets anders om vriendelijker te zijn voor CPU's en de levensduur van de batterij.

Staatspeiling

Dit is het meest voorkomende (mis)gebruik van timers, waarbij ze worden gebruikt om voortdurend te controleren of te peilen of er iets is veranderd. In de meeste gevallen is er een push- equivalent, waarbij het ding je vertelt over de verandering wanneer deze plaatsvindt, zodat je niet steeds hoeft te controleren. Kijk of er een evenement is dat hetzelfde bereikt.

Enkele voorbeelden:

Er zijn ook meldingstriggers als u op een bepaald tijdstip een melding wilt weergeven.

Animatie

Animatie is een visueel iets, dus het mag geen CPU-tijd gebruiken als de pagina verborgen is.

requestAnimationFrame is veel beter in het plannen van animatiewerk dan JavaScript-timers. Het synchroniseert met de vernieuwingsfrequentie van het apparaat, zodat u slechts één callback krijgt per weer te geven frame, en u de maximale hoeveelheid tijd krijgt om dat frame samen te stellen. Bovendien wacht requestAnimationFrame tot de pagina zichtbaar is, zodat er geen CPU wordt gebruikt wanneer de pagina verborgen is.

Als u uw hele animatie vooraf kunt declareren, overweeg dan om CSS-animaties of de webanimatie-API te gebruiken. Deze hebben dezelfde voordelen als requestAnimationFrame , maar de browser kan aanvullende optimalisaties uitvoeren, zoals automatische compositie, en ze zijn over het algemeen gemakkelijker te gebruiken.

Als je animatie een lage framerate heeft (zoals een knipperende cursor), zijn timers op dit moment nog steeds de beste optie, maar je kunt ze combineren met requestAnimationFrame om het beste van twee werelden te krijgen:

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);
}

Gebruik:

const controller = new AbortController();

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

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

Testen

Deze wijziging wordt ingeschakeld voor alle Chrome-gebruikers in Chrome 88 (januari 2021). Het is momenteel ingeschakeld voor 50% van de Chrome Bèta-, Dev- en Canary-gebruikers. Als u het wilt testen, gebruikt u deze opdrachtregelvlag wanneer u Chrome Beta, Dev of Canary start:

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

Het argument grace_period_seconds/10 zorgt ervoor dat intense throttling in werking treedt na 10 seconden nadat de pagina verborgen is, in plaats van de volledige 5 minuten, waardoor het gemakkelijker wordt om de impact van de throttling te zien.

De toekomst

Omdat timers een bron zijn van overmatig CPU-gebruik, blijven we zoeken naar manieren waarop we ze kunnen beperken zonder de webinhoud kapot te maken, en naar API's die we kunnen toevoegen/wijzigen om aan gebruiksscenario's te voldoen. Persoonlijk zou ik de noodzaak van animationInterval willen elimineren ten gunste van efficiënte laagfrequente animatie-callbacks. Als je vragen hebt, neem dan gerust contact met mij op via Twitter !

Headerfoto door Heather Zabriskie op Unsplash .