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

Jake Archibald
Jake Archibald

O Chrome 88 (janeiro de 2021) vai limitar muito os timers de JavaScript encadeados para páginas ocultas em condições específicas. Isso vai reduzir o uso da CPU, o que também reduz o uso da bateria. Há alguns casos extremos em que isso vai mudar o comportamento, mas os timers são usados com frequência em que uma API diferente seria mais eficiente e confiável.

Ok, isso foi muito técnico e um pouco ambíguo. Vamos lá…

Terminologia

Páginas ocultas

Geralmente, oculto 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 não estiver totalmente visível. Alguns navegadores vão mais longe do que outros, mas você pode sempre usar a API Page Visibility para rastrear quando o navegador achar que a visibilidade mudou.

Temporizadores JavaScript

Por timers do JavaScript, quero dizer setTimeout e setInterval, que permitem programar um callback no futuro. Os timers são úteis e não vão desaparecer, mas às vezes são usados para consultar o estado quando um evento é mais eficiente e 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 o controle de taxa funciona

O estrangulamento ocorre em etapas:

Limitação mínima

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

  • A página está visível.
  • A página fez ruídos nos últimos 30 segundos. Isso pode ser de qualquer uma das APIs de criaçã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 4 ms e a contagem da cadeia seja 5 ou mais. Nesse caso, o tempo limite é definido como 4 ms. Isso não é novo. Os navegadores fazem isso há muitos anos.

Limitação

Isso acontece com os timers programados quando o limite mínimo não é aplicado e qualquer das seguintes condições é verdadeira:

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

O navegador vai verificar os timers nesse grupo uma vez por segundo. Como eles são verificados apenas uma vez por segundo, os timers com um tempo limite semelhante serã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.

Limitação intensa

Aqui estão as novidades do Chrome 88. A limitação intensa acontece em timers que são 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á escondida há mais de cinco minutos.
  • O número de cadeias é 5 ou maior.
  • A página ficou em silêncio por pelo menos 30 segundos.
  • O WebRTC não está em uso.

Nesse caso, o navegador vai 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 para um timer, ou os timers podem ser combinados com outra coisa para serem mais gentis com as CPUs e a duração da bateria.

Enquete de estado

Esse é o uso (in)correto mais comum de timers, em que eles são usados para verificar ou pesquisar continuamente se algo mudou. Na maioria dos casos, há um equivalente de envio, em que a coisa informa sobre a mudança quando ela acontece, para que você não precise verificar sempre. Verifique se há um evento que faz a mesma coisa.

Alguns exemplos:

Também há gatilhos de notificação se você quiser mostrar uma notificação em um determinado horário.

Animação

A animação é uma coisa visual, então ela não deve usar o tempo da CPU quando a página está escondida.

requestAnimationFrame é muito melhor em programar trabalhos de animação do que os timers do JavaScript. Ele sincroniza com a taxa de atualização do dispositivo, garantindo que você receba apenas um callback por frame visível e que receba o tempo máximo para criar esse frame. Além disso, requestAnimationFrame vai esperar a página ficar visível para não usar a CPU quando a página estiver oculta.

Se você puder declarar toda a animação com antecedência, use animações CSS ou a API Web Animations. Elas têm as mesmas vantagens que requestAnimationFrame, mas o navegador pode realizar otimizações adicionais, como composição automática, e geralmente são mais fáceis de usar.

Se a animação tiver baixa taxa de frames (como um cursor piscando), os timers ainda são a melhor opção no momento, mas você pode combiná-los com requestAnimationFrame para aproveitar 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();

Teste

Essa mudança será ativada para todos os usuários do Chrome 88 (janeiro de 2021). No momento, ele está ativado para 50% dos usuários do Chrome Beta, Dev e Canary. Se quiser testar, use esta flag 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 o limite intenso seja ativado após 10 segundos da página ser oculta, em vez de 5 minutos, facilitando a visualização do impacto do limite.

O futuro

Como os timers são uma fonte de uso excessivo de CPU, vamos continuar procurando maneiras de limitar o uso sem interromper o conteúdo da Web e APIs que podem ser adicionadas/alteradas para atender aos casos de uso. Pessoalmente, gostaria de eliminar a necessidade de animationInterval em favor de callbacks de animação de baixa frequência eficientes. Se tiver dúvidas, entre em contato comigo no Twitter.

Foto do cabeçalho de Heather Zabriskie no Unsplash.