Uma nova API JavaScript que pode ajudar a evitar o equilíbrio entre a performance de carregamento e a capacidade de resposta de entrada.
É difícil carregar rapidamente. Os sites que usam JS para renderizar o conteúdo atualmente precisam fazer um trade-off entre a performance de carregamento e a capacidade de resposta de entrada: ou executam todo o trabalho necessário para exibição de uma só vez (melhor desempenho de carregamento, pior capacidade de resposta de entrada) ou dividem 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()
foi enviado em navegadores baseados no Chromium a partir da versão 87.
Nenhum outro navegador sinalizou a 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 responsividade) 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 vai processar o evento de entrada de clique 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 verificar a fila de eventos de entrada e conferir se há algo 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 devolve o controle 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
verifique a entrada sem ceder ao 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 ser executado 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
extras 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 houver interesse em processar nada
além da entrada (por exemplo, pintura) até que o trabalho seja concluído, também é possível aumentar
a duração de QUANTUM
.
Por padrão, os eventos "contínuos" não são retornados de isInputPending()
. Eles
incluem mousemove
, pointermove
e outros. Se você também quiser ceder o acesso a
esses recursos, não tem problema. Ao fornecer um objeto para isInputPending()
com
includeContinuous
definido como true
, podemos começar:
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 beneficiar do isInputPending()
nos bastidores sem reescritas significativas.
Ceder não é sempre ruim
Vale a pena notar 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. Certifique-se de que você está gerando com frequência suficiente se
o site exigir interações com subframes estilizados.
Também é preciso ter cuidado com outras páginas que 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 com a possibilidade de os desenvolvedores
começarem a usá-lo hoje. 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 a enviar.
Foto principal de Will H McMahan no Unsplash.