Melhor programação de JS com isInputPending()

Uma nova API JavaScript que pode ajudar a evitar o equilíbrio entre desempenho de carregamento e capacidade de resposta de entrada.

Nate Schloss
Nate Schloss
Andrew Comminos
Andrew Comminos

Carregar rápido é difícil. Sites que usam JavaScript para renderizar conteúdo atualmente é preciso escolher entre o desempenho de carga e o responsividade: realize todo o trabalho necessário para exibir de uma só vez (melhor desempenho de carregamento, menor capacidade de resposta a entradas) ou dividir o trabalho em tarefas menores para permanecer responsivo entrada e gravação (pior desempenho de carregamento, melhor entrada capacidade de resposta).

Para eliminar essa necessidade, o Facebook propôs e implementou a API isInputPending() no Chromium para melhorar a capacidade de resposta sem rendendo. Com base no feedback do teste de origem, fizemos várias atualizações na API, e temos o prazer de anunciar que agora ela é disponibilizada por padrão no Chromium. 87!

Compatibilidade com navegadores

Compatibilidade com navegadores

  • Chrome: 87.
  • Borda: 87.
  • Firefox: incompatível.
  • Safari: incompatível.

Origem

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 maior parte 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 desenvolvedores, mas a experiência do usuário (em especial, a capacidade de resposta) podem sofrer drasticamente se o script for executado por um longo tempo de resposta. Se a página estiver fazendo muito trabalho enquanto um evento de entrada é acionado, por exemplo, a página não processará o evento de entrada de clique até que esse trabalho é concluída.

A prática recomendada atual é lidar com esse problema quebrando o o JavaScript em blocos menores. Enquanto a página é carregada, ela pode executar uma um pouco de JavaScript e depois gerar e passar o controle de volta para o navegador. A navegador poderá verificar a fila de eventos de entrada e conferir se há algo ele precisa informar à página. Em seguida, o navegador pode voltar a executar a O JavaScript bloqueia à medida que são adicionados. Isso ajuda, mas pode causar outros problemas.

Sempre que a página retorna o controle ao navegador, leva algum tempo o navegador para verificar a fila de eventos de entrada, processar eventos e selecionar os próximos bloco de JavaScript. O navegador responde aos eventos mais rapidamente, mas o o tempo de carregamento da página fica lento. E, se nos rendermos com muita frequência, a página carrega muito lentamente. Se a redução da frequência, o navegador demorará mais para reagem a eventos do usuário, e isso faz com que as pessoas fiquem frustradas. Nada divertido.

Um diagrama mostrando que, quando você executa tarefas longas de JS, o navegador tem menos tempo para despachar eventos.

No Facebook, queríamos saber como seria se tivéssemos um abordagem nova de carregamento que eliminaria essa escolha frustrante. Qa entramos em contato com nossos amigos do Chrome sobre isso e nos apresentou a proposta para isInputPending(). A API isInputPending() é a primeira a usar o conceito de interrompe entradas do usuário na Web e permite que o JavaScript seja capazes de verificar entradas sem abrir mão do navegador.

Um diagrama mostrando que isInputPending() permite que o JS verifique se há uma entrada pendente do usuário, sem retornar completamente a execução ao navegador.

Como houve interesse na API, fizemos uma parceria com nossos colegas do Chrome. para implementar e enviar o recurso no Chromium. Com a ajuda do Chrome para engenheiros, temos os patches aplicados em um teste de origem (uma forma de o Chrome testar as alterações e receber feedback dos desenvolvedores antes do lançamento completo de uma API).

Recebemos feedback do teste de origem e de outros membros da W3C Web Performance Working Group e implementaram mudanças na API.

Exemplo: um programador de rendimento

Vamos supor que você tenha que executar vários comandos de bloqueio de exibição para carregar página, por exemplo, gerando marcações a partir de componentes, fatorando primos ou um ícone de carregamento. Cada um deles é dividido em item de trabalho. Usando o padrão do programador, vamos esboçar como processar nosso trabalho em uma função processWorkQueue() hipotética:

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

Invocando processWorkQueue() mais tarde em uma nova macrotarefa usando setTimeout(), dar ao navegador a capacidade de permanecer um pouco responsivo a entradas (ele pode executar manipuladores de eventos antes que o trabalho seja retomado) enquanto ainda consegue executar relativamente sem interrupções. No entanto, outros trabalhos podem nos desviar por muito tempo que queira controlar o loop de eventos ou consiga até QUANTUM milissegundos extras de latência de eventos.

Está tudo bem, mas podemos melhorar? Claro!

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 mais rapidamente às entradas, sem deixar de garantir que nosso trabalho de bloqueio será executada sem interrupções. Se não estivermos interessados em lidar com nada além da entrada (por exemplo, pintura) até que o trabalho esteja concluído, podemos aumentar também o comprimento de QUANTUM.

Por padrão, "contínuo" eventos não são retornados de isInputPending(). Esses incluem mousemove, pointermove, entre outros. Se você tiver interesse em gerar sem problemas. Ao fornecer um objeto para isInputPending() com includeContinuous definido como true, pronto:

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() nos próprios as principais bibliotecas de programação usando uma lógica parecida. Espero que isso leve desenvolvedores que usam esses frameworks para se beneficiar do isInputPending() nos bastidores sem reescritas significativas.

Rendimento nem sempre é ruim

Vale ressaltar que produzir menos não é a solução certa para todos os usos. caso. Há muitas razões para retornar o controle ao navegador além de processar eventos de entrada, como a renderização e a execução de outros scripts da página.

Há casos em que o navegador não consegue atribuir os atributos pendentes eventos de entrada. Especificamente, a configuração de clipes e máscaras complexos para origens cruzadas Os iframes podem informar falsos negativos (ou seja, isInputPending() pode retornar inesperadamente false ao segmentar esses frames). Certifique-se de estar rendendo com frequência suficiente se seu site exige interações com subframes estilizados.

Esteja atento a outras páginas que também compartilham um loop de eventos. Em plataformas como como o Chrome para Android, é comum que várias origens compartilhem um evento repetição. isInputPending() nunca vai retornar true se a entrada for enviada para um de origem cruzada. Assim, as páginas em segundo plano podem interferir na a capacidade de resposta das páginas em primeiro plano. Talvez você queira reduzir, adiar ou produzir com mais frequência ao trabalhar em segundo plano usando a API Page Visibility.

Recomendamos que você use o isInputPending() com cautela. Se não houver trabalho de bloqueio de usuários a ser feito e, em seguida, seja gentil com os outros no loop de eventos, produzindo com mais frequência. Tarefas longas podem ser prejudiciais.

Feedback

  • Deixe seu feedback sobre as especificações na repositório is-input-pending.
  • Entre em contato com @acomminos (um dos autores das especificações). no Twitter.

Conclusão

Estamos felizes com o lançamento do isInputPending() e que os desenvolvedores possam para começar a usá-la hoje mesmo. Essa API é a primeira vez que o Facebook cria uma a nova API da Web, da concepção da ideia à proposta de padrões, envio em um navegador. Gostaríamos de agradecer a todos que nos ajudaram a alcançar esse e agradecer a todos do Chrome que nos ajudaram a desenvolver a ideia e enviá-la.

Foto hero de Will H McMahan no Abrir a página.