Limitazione intensiva dei timer JS concatenati a partire da Chrome 88

Jake Archibald
Jake Archibald

Chrome 88 (gennaio 2021) limiterà molto i timer JavaScript concatenati per le pagine nascoste in determinate condizioni. Ciò ridurrà l'utilizzo della CPU e l'utilizzo della batteria. Ci sono alcuni casi limite in cui il comportamento cambia, ma i timer vengono spesso utilizzati laddove un'API diversa sarebbe più efficiente e più affidabile.

Ok, era un termine piuttosto pesante in gergo e un po' ambiguo. Vediamo in dettaglio...

Terminologia

Pagine nascoste

In genere, nascosto significa che una scheda diversa è attiva o che la finestra è stata ridotta a icona, ma i browser potrebbero considerare una pagina nascosta ogni volta che i relativi contenuti sono completamente non visibili. Alcuni browser si spingono oltre di altri qui, ma puoi sempre usare l'API di visibilità delle pagine per monitorare quando il browser ritiene che la visibilità sia cambiata.

Timer JavaScript

Con Timer JavaScript intendo setTimeout e setInterval, che consentono di pianificare una richiamata in futuro. I timer sono utili e non scompaiono, ma a volte vengono utilizzati per eseguire il polling dello stato quando un evento sarebbe più efficiente e più preciso.

Timer concatenati

Se chiami setTimeout nella stessa attività di un callback setTimeout, la seconda chiamata è "concatenata". Con setInterval, ogni iterazione fa parte della catena. Questo potrebbe essere più facile da capire con il codice:

let chainCount = 0;

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

E:

let chainCount = 0;

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

Come funziona la limitazione

La limitazione avviene in più fasi:

Limitazione minima

Questo accade ai timer pianificati quando si verifica una qualsiasi delle seguenti condizioni:

  • La pagina è visibile.
  • Nella pagina sono stati emessi rumori negli ultimi 30 secondi. L'audio può provenire da una qualsiasi delle API per la creazione di suoni, ma non viene conteggiata una traccia audio silenziosa.

Il timer non viene limitato, a meno che il timeout richiesto non sia inferiore a 4 ms e il conteggio delle catene sia pari o superiore a 5, nel qual caso il timeout è impostato su 4 ms. Non è una novità; i browser lo fanno da molti anni.

Limitazione

Questo accade ai timer pianificati quando non vengono applicate limitazioni minime e si verifica una qualsiasi delle seguenti condizioni:

  • Il conteggio delle catene è inferiore a 5.
  • La pagina è nascosta da meno di 5 minuti.
  • WebRTC è in uso. Nello specifico, c'è un RTCPeerConnection con RTCDataChannel "aperto" o MediaStreamTrack "pubblicato".

Il browser controllerà i timer in questo gruppo una volta al secondo. Poiché vengono selezionati solo una volta al secondo, i timer con un timeout simile verranno raggruppati, consolidando il tempo necessario per l'esecuzione del codice nella scheda. Non è una novità; i browser lo fanno in una certa misura da anni.

Limitazione intensa

Ok, ecco la nuova versione di Chrome 88. Ai timer programmati viene applicata una limitazione intensiva quando non viene applicata nessuna delle condizioni di limitazione minima o limitazione e se vengono soddisfatte tutte le seguenti condizioni:

  • La pagina è nascosta da più di 5 minuti.
  • Il conteggio delle catene è pari o superiore a 5.
  • La pagina è stata silenziosa per almeno 30 secondi.
  • WebRTC non è in uso.

In questo caso, il browser controllerà i timer in questo gruppo una volta al minuto. Analogamente a prima, ciò significa che i timer verranno raggruppati in questi controlli minuto per minuto.

Soluzioni alternative

Di solito esiste un'alternativa migliore a un timer, oppure i timer possono essere combinati con qualcos'altro per rispettare le CPU e la durata della batteria.

Sondaggi statali

Si tratta dell'uso più comune (uso improprio) dei timer, che vengono utilizzati per effettuare controlli continui o per eseguire sondaggi per vedere se qualcosa è cambiato. Nella maggior parte dei casi esiste un'opzione equivalente per il formato push, in cui l'elemento ti informa della modifica quando si verifica, senza dover continuare a controllare. Verifica se esiste un evento che raggiunge lo stesso obiettivo.

Ecco alcuni esempi:

Esistono anche degli attivatori di notifiche se vuoi mostrare una notifica in un momento specifico.

Animazione

L'animazione è un elemento visivo, quindi non dovrebbe utilizzare il tempo di CPU quando la pagina è nascosta.

requestAnimationFrame è molto più utile nella pianificazione dell'animazione rispetto ai timer JavaScript. Si sincronizza con la frequenza di aggiornamento del dispositivo, assicurandoti di ricevere un solo callback per frame visualizzabile e il tempo massimo per creare quel frame. Inoltre, requestAnimationFrame attenderà che la pagina sia visibile, quindi non utilizza alcuna CPU quando la pagina è nascosta.

Se puoi dichiarare l'intera animazione in anticipo, valuta l'utilizzo di animazioni CSS o dell'API per le animazioni web. Queste soluzioni presentano gli stessi vantaggi di requestAnimationFrame, ma il browser può eseguire ottimizzazioni aggiuntive, come la composizione automatica, e sono generalmente più semplici da utilizzare.

Se l'animazione ha una frequenza fotogrammi bassa (ad esempio un cursore lampeggiante), i timer sono ancora l'opzione migliore in questo momento, ma puoi combinarli con requestAnimationFrame per ottenere il meglio da entrambe le modalità:

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

Uso:

const controller = new AbortController();

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

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

Test

Questo cambiamento verrà attivato per tutti gli utenti in Chrome 88 (gennaio 2021). Attualmente è attivo per il 50% degli utenti di Chrome Beta, Dev e Canary. Se vuoi testarlo, utilizza questo flag della riga di comando quando avvii Chrome Beta, Dev o Canary:

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

L'argomento grace_period_seconds/10 determina un'attivazione intensiva della limitazione dopo 10 secondi di occultamento della pagina, anziché l'intera durata di 5 minuti, rendendo più facile vedere l'impatto della limitazione.

Il futuro

Poiché i timer sono una fonte di utilizzo eccessivo della CPU, continueremo a cercare modi per limitarli senza danneggiare i contenuti web e che possiamo aggiungere/modificare per soddisfare i casi d'uso. Personalmente, vorrei eliminare la necessità di animationInterval in favore di callback di animazione a bassa frequenza efficienti. In caso di domande, contattaci su Twitter.

Foto dell'intestazione di Heather Zabriskie su Unsplash.