Uma nova API JavaScript que pode ajudar a evitar a compensação entre desempenho de carregamento e capacidade de resposta de entrada.
O carregamento rápido é difícil. No momento, os sites que usam JS para renderizar o conteúdo precisam escolher entre o desempenho de carregamento e a capacidade de resposta da entrada: realizar todo o trabalho necessário para exibição de uma só vez (melhor desempenho de carregamento, pior capacidade de resposta de entrada) ou dividir o trabalho em tarefas menores para permanecer responsivo à entrada e à pintura (pior desempenho de carregamento, melhor capacidade de resposta de entrada).
Para eliminar a necessidade de fazer esse trade-off, o Facebook propôs e implementou
a API isInputPending()
no Chromium para melhorar a capacidade de resposta sem
renderizar. Com base no feedback do teste de origem, fizemos várias atualizações na
API e temos o prazer de anunciar que ela agora é enviada por padrão no Chromium
87.
Compatibilidade com navegadores
O isInputPending()
é enviado em navegadores baseados no Chromium a partir da versão 87.
Nenhum outro navegador sinalizou uma intenção de enviar a API.
Contexto
A maioria do trabalho no ecossistema JS atual é feita em uma única linha de execução: a principal. Isso fornece um modelo de execução robusto para os desenvolvedores, mas a experiência do usuário (principalmente a capacidade de resposta) pode ser afetada drasticamente se o script for executado por um longo tempo. Se a página estiver fazendo muito trabalho enquanto um evento de entrada é acionado, por exemplo, ela não processará o evento de entrada até que esse trabalho seja concluído.
A prática recomendada atual é lidar com esse problema dividindo o JavaScript em blocos menores. Enquanto a página está sendo carregada, ela pode executar um pouco de JavaScript e, em seguida, ceder e transmitir o controle de volta ao navegador. O navegador pode então verificar a fila de eventos de entrada e ver se há algo sobre o que ele precisa informar à página. Em seguida, o navegador pode voltar a executar os blocos JavaScript conforme eles são adicionados. Isso ajuda, mas pode causar outros problemas.
Cada vez que a página passa o controle de volta ao navegador, leva algum tempo para que ele verifique a fila de eventos de entrada, processe eventos e selecione o próximo bloco de JavaScript. Embora o navegador responda aos eventos mais rapidamente, o tempo de carregamento geral da página fica mais lento. E se fizermos isso com muita frequência, a página vai carregar muito lentamente. Se fizermos isso com menos frequência, o navegador vai demorar mais para responder aos eventos do usuário, e as pessoas vão ficar frustradas. Não é divertido.
No Facebook, queríamos saber como seria se criássemos uma
nova abordagem de carregamento que eliminasse esse trade-off frustrante. Entramos em contato com nossos amigos do Chrome e criamos a proposta
para isInputPending()
. A API isInputPending()
é a primeira a usar o conceito de
interrupções para entradas do usuário na Web e permite que o JavaScript possa
verificar as entradas sem gerar resultados no navegador.
Como houve interesse na API, fizemos parceria com nossos colegas do Chrome para implementar e enviar o recurso no Chromium. Com a ajuda dos engenheiros do Chrome, os patches foram lançados após um teste de origem, que é uma forma de o Chrome testar mudanças e receber feedback dos desenvolvedores antes de lançar uma API.
Agora, coletamos o feedback do teste de origem e dos outros membros do grupo de trabalho de desempenho da Web do W3C e implementamos mudanças na API.
Exemplo: um programador de rendimento
Suponha que você tenha um monte de trabalho de bloqueio de exibição para carregar sua
página, por exemplo, gerando marcação de componentes, fatorando números primos ou
apenas exibindo um ícone de carregamento legal. Cada um deles é dividido em um item de trabalho
discreto. Usando o padrão do programador, vamos esboçar como processar
nosso trabalho em uma função hipotética processWorkQueue()
:
const DEADLINE = performance.now() + QUANTUM;
while (workQueue.length > 0) {
if (performance.now() >= DEADLINE) {
// Yield the event loop if we're out of time.
setTimeout(processWorkQueue);
return;
}
let job = workQueue.shift();
job.execute();
}
Ao invocar processWorkQueue()
mais tarde em uma nova macrotarefa usando setTimeout()
, permitimos que o navegador continue um pouco responsivo à entrada (ele pode
executar manipuladores de eventos antes que o trabalho seja retomado) e ainda consiga executar de forma
ininterrupta. No entanto, podemos ter a programação cancelada por um longo período por outro trabalho
que queira controlar o loop de eventos ou ter até QUANTUM
milissegundos
extra de latência de evento.
Isso está bom, mas podemos melhorar? Sim!
const DEADLINE = performance.now() + QUANTUM;
while (workQueue.length > 0) {
if (navigator.scheduling.isInputPending() || performance.now() >= DEADLINE) {
// Yield if we have to handle an input event, or we're out of time.
setTimeout(processWorkQueue);
return;
}
let job = workQueue.shift();
job.execute();
}
Ao introduzir uma chamada para navigator.scheduling.isInputPending()
, podemos
responder à entrada mais rapidamente, garantindo que nosso trabalho de bloqueio de exibição
seja executado sem interrupções. Se não quisermos processar nada
além da entrada (por exemplo, pintura) até que o trabalho seja concluído, também podemos aumentar
o comprimento de QUANTUM
.
Por padrão, eventos "contínuos" não são retornados de isInputPending()
. Eles
incluem mousemove
, pointermove
e outros. Se você também tiver interesse em ceder o acesso a
esses recursos, não tem problema. Ao fornecer um objeto para isInputPending()
com includeContinuous
definido como true
, estamos prontos:
const DEADLINE = performance.now() + QUANTUM;
const options = { includeContinuous: true };
while (workQueue.length > 0) {
if (navigator.scheduling.isInputPending(options) || performance.now() >= DEADLINE) {
// Yield if we have to handle an input event (any of them!), or we're out of time.
setTimeout(processWorkQueue);
return;
}
let job = workQueue.shift();
job.execute();
}
Pronto! Frameworks como o React estão criando suporte para isInputPending()
nas
bibliotecas de programação principais usando uma lógica semelhante. Esperamos que isso leve
os desenvolvedores que usam esses frameworks a se beneficiarem de isInputPending()
nos bastidores sem reescritas significativas.
Ceder não é sempre ruim
Vale ressaltar que produzir menos não é a solução certa para todos os casos de uso. Há muitos motivos para retornar o controle ao navegador, além de processar eventos de entrada, como renderizar e executar outros scripts na página.
Há casos em que o navegador não consegue atribuir corretamente eventos de entrada
pendentes. Em particular, definir clipes e máscaras complexos para iframes
de origem cruzada pode gerar falsos negativos. Ou seja, isInputPending()
pode retornar
false inesperadamente ao segmentar esses frames. Verifique se você está gerando resultados com frequência suficiente
caso seu site exija interações com subframes estilizados.
Esteja atento a outras páginas que também compartilham um loop de eventos. Em plataformas como
o Chrome para Android, é bastante comum que várias origens compartilhem um loop
de eventos. isInputPending()
nunca vai retornar true
se a entrada for enviada para um
frame de origem cruzada. Portanto, as páginas em segundo plano podem interferir na
responsividade das páginas em primeiro plano. Talvez você queira reduzir, adiar ou ceder
com mais frequência ao trabalhar em segundo plano usando a API Page Visibility.
Recomendamos que você use isInputPending()
com discrição. Se não houver
trabalho de bloqueio do usuário a ser feito, seja gentil com os outros no loop de eventos
retornando com mais frequência. Tarefas longas podem ser prejudiciais.
Feedback
- Deixe feedback sobre a especificação no repositório is-input-pending.
- Entre em contato com @acomminos (um dos autores da especificação) no Twitter.
Conclusão
Estamos felizes com o lançamento do isInputPending()
e que os desenvolvedores possam
começar a usá-lo hoje mesmo. Essa é a primeira vez que o Facebook cria uma
nova API da Web e a leva da incubação de ideias à proposta de padrões para
ser lançada em um navegador. Gostaríamos de agradecer a todos que nos ajudaram a chegar a este
ponto e dar um destaque especial a todos no Chrome que nos ajudaram a dar vida
a essa ideia e fazer o lançamento!
Foto principal de Will H McMahan no Unsplash.