Limitação intensa de timers do JS encadeados a partir do Chrome 88

Jake Archibald
Jake Archibald

O Chrome 88 (janeiro de 2021) limitará os timers do JavaScript encadeados para páginas ocultas em determinadas condições. Isso reduzirá o uso da CPU, o que também reduzirá o uso da bateria. Há alguns casos extremos em que isso muda o comportamento, mas os timers são frequentemente usados, quando uma API diferente seria mais eficiente e mais confiável.

Certo, esse era um jargão meio ambíguo. Vamos lá...

Terminologia

Páginas ocultas

Geralmente, oculta significa que uma guia diferente está ativa ou que a janela foi minimizada, mas os navegadores podem considerar uma página oculta sempre que o conteúdo dela está totalmente não visível. Alguns navegadores vão mais além do que outros aqui, mas você sempre pode usar a API de visibilidade de páginas para acompanhar quando o navegador acredita que houve uma mudança na visibilidade.

Timers do JavaScript

Por Temporizadores do JavaScript quero dizer setTimeout e setInterval, que permitem programar um callback em algum momento no futuro. Os timers são úteis e não são desativados, mas às vezes são usados para pesquisar o estado em que um evento seria mais eficiente e mais preciso.

Timers encadeados

Se você chamar setTimeout na mesma tarefa que um callback setTimeout, a segunda invocação será "encadeada". Com setInterval, cada iteração faz parte da cadeia. Isso pode ser mais fácil de entender com o código:

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

Como a limitação funciona

A limitação acontece em etapas:

Limitação mínima

Isso acontece com timers programados quando qualquer uma das seguintes condições é verdadeira:

  • A página está visível.
  • A página fez barulho nos últimos 30 segundos. Isso pode ser de qualquer uma das APIs de produção de som, mas uma faixa de áudio silenciosa não conta.

O timer não é limitado, a menos que o tempo limite solicitado seja menor que 4ms e a contagem de cadeias seja 5 ou maior. Nesse caso, o tempo limite é definido como 4ms. Isso não é novo, já que os navegadores já fazem isso há muitos anos.

Limitação

Isso acontece com os timers programados quando a limitação mínima não se aplica, e qualquer uma das seguintes condições é verdadeira:

  • A contagem de cadeias for menor que 5.
  • A página está oculta há menos de cinco minutos.
  • O WebRTC está em uso. Especificamente, há um RTCPeerConnection com um RTCDataChannel "aberto" ou um MediaStreamTrack "ativo".

O navegador verificará os timers neste grupo uma vez por segundo. Como eles são verificados apenas uma vez por segundo, os timers com um tempo limite semelhante são agrupados, consolidando o tempo que a guia precisa para executar o código. Isso também não é novo. Os navegadores fazem isso há anos, até certo ponto.

Limitação intensa

Ok, aqui está a nova parte do Chrome 88. A limitação intensa acontece com os timers programados quando nenhuma das condições de limitação mínima ou limitação se aplica, e todas as condições a seguir são verdadeiras:

  • A página está oculta há mais de cinco minutos.
  • A contagem de cadeias é 5 ou mais.
  • A página ficou em silêncio por pelo menos 30 segundos.
  • O WebRTC não está em uso.

Nesse caso, o navegador verificará os timers nesse grupo uma vez a cada minuto. Assim como antes, isso significa que os timers serão agrupados nessas verificações minuto a minuto.

Alternativas

Geralmente, há uma alternativa melhor ao timer, ou os timers podem ser combinados com algo mais para serem mais favoráveis às CPUs e à duração da bateria.

Pesquisas de estado

Esse é o uso mais comum (erro) de timers, em que eles são usados para verificar ou pesquisar continuamente para ver se algo mudou. Na maioria dos casos, há um push equivalente, em que a coisa informa sobre a mudança quando ela acontece, de modo que você não precisa continuar verificando. Verifique se há um evento que realiza o mesmo resultado.

Alguns exemplos:

Há também acionadores de notificação, caso você queira mostrar uma notificação em um momento específico.

Animação

A animação é visual e não pode usar o tempo de CPU quando a página está oculta.

requestAnimationFrame é muito melhor para programar trabalhos de animação do que timers do JavaScript. Ela é sincronizada com a taxa de atualização do dispositivo, garantindo que você receba apenas um callback por frame que possa ser exibido e que você tenha o tempo máximo para criar esse frame. Além disso, o requestAnimationFrame vai aguardar até que a página fique visível para que não use nenhuma CPU quando a página estiver oculta.

Se você puder declarar toda a animação antecipadamente, considere usar animações CSS ou a API Web Animation. Elas têm as mesmas vantagens de requestAnimationFrame, mas o navegador pode realizar outras otimizações, como a composição automática, e elas geralmente são mais fáceis de usar.

Se a animação tiver um frame rate baixo (como um cursor piscando), os timers ainda são a melhor opção no momento, mas eles podem ser combinados com requestAnimationFrame para ter o melhor dos dois mundos:

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

testes

Essa mudança será ativada para todos os usuários no Chrome 88 (janeiro de 2021). No momento, ela está ativada para 50% dos usuários do Chrome Beta, Dev e Canary. Se você quiser testá-lo, use esta sinalização de linha de comando ao iniciar o Chrome Beta, Dev ou Canary:

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

O argumento grace_period_seconds/10 faz com que a limitação intensa seja iniciada após 10 segundos da página sendo ocultada, em vez dos 5 minutos completos, facilitando a visualização do impacto da limitação.

O futuro

Como os timers são uma fonte de uso excessivo da CPU, continuaremos a analisar as maneiras de limitá-los sem corromper o conteúdo da Web e as APIs que podemos adicionar/mudar para atender a casos de uso. Pessoalmente, quero eliminar a necessidade de animationInterval em favor de callbacks de animação de baixa frequência eficientes. Se tiver alguma dúvida, entre em contato comigo no Twitter.

Foto de capa de Heather Zabriskie no Unsplash.