Mejor programación de JS con isInputPending()

Una nueva API de JavaScript que puede ayudarte a evitar el equilibrio entre el rendimiento de carga y la capacidad de respuesta de la entrada.

Nate Schloss
Nate Schloss
Andrew Comminos
Andrew Comminos

Cargarlo rápido es difícil. Sitios que aprovechan JS para procesar su contenido actualmente entre el rendimiento de carga y las entradas capacidad de respuesta: se debe realizar todo el trabajo necesario para la de una sola vez (mejor rendimiento de carga, peor capacidad de respuesta a la entrada) o dividir el trabajo en tareas más pequeñas para que sea adaptable entrada y pintura (peor rendimiento de carga, mejor entrada la capacidad de respuesta).

Para eliminar la necesidad de hacer esta compensación, Facebook propuso e implementó la API de isInputPending() en Chromium para mejorar la capacidad de respuesta sin rindiendo. Según los comentarios que recibimos sobre la prueba de origen, aplicamos una serie de actualizaciones al API y nos complace anunciar que la API ahora se envía de forma predeterminada en Chromium ¡87!

Compatibilidad del navegador

Navegadores compatibles

  • Chrome: 87.
  • Borde: 87.
  • Firefox: No es compatible.
  • Safari: no es compatible.

Origen

isInputPending() se envió en navegadores basados en Chromium a partir de la versión 87. Ningún otro navegador indicó la intención de enviar la API.

Información general

La mayor parte del trabajo en el ecosistema JS actual se realiza en un solo subproceso: el principal. Esto proporciona un modelo de ejecución sólido a los desarrolladores, pero la experiencia del usuario (la capacidad de respuesta en particular) puede verse afectada drásticamente si la secuencia de comandos se ejecuta durante un período prolongado tiempo. Si la página hace mucho trabajo mientras se activa un evento de entrada, Por ejemplo, la página no controlará el evento de entrada de clic hasta que de datos completados.

En la actualidad, la práctica recomendada es abordar este problema rompiendo los JavaScript en bloques más pequeños. Mientras se carga la página, esta puede ejecutar una un poco de JavaScript, y luego ceder y pasar el control al navegador. El el navegador puede comprobar la cola de eventos de entrada y ver si tiene que informar a la página. Luego, el navegador puede volver a ejecutar Se bloquea JavaScript a medida que se agregan. Esto es útil, pero puede causar otros problemas.

Cada vez que la página le devuelve el control al navegador, se necesita un tiempo para el navegador para comprobar la cola de eventos de entrada, procesar eventos y retomar Bloque de JavaScript. Si bien el navegador responde a los eventos más rápido, el el tiempo de carga de la página se ralentiza. Y si cederemos con demasiada frecuencia, la página se carga demasiado lento. Si rindemos menos seguido, el navegador tardará más en a los eventos de los usuarios y las personas se frustran. No es divertido.

Un diagrama que muestra que cuando ejecutas tareas largas de JS, el navegador tiene menos tiempo para enviar eventos.

En Facebook, queríamos saber cómo sería el panorama un nuevo enfoque de carga que eliminaría esta frustrante compensación. Mié se comunicó con nuestros amigos de Chrome sobre esto y idearon la propuesta para isInputPending(). La API de isInputPending() es la primera en usar el concepto de interrumpe las entradas del usuario en la Web y permite que se ejecute pueda comprobar la entrada sin ceder al navegador.

Un diagrama que muestra que isInputPending() permite a tu JS verificar si hay entradas del usuario pendientes, sin ceder por completo la ejecución al navegador.

Como había interés en la API, nos asociamos con nuestros colegas en Chrome. para implementar y enviar la función en Chromium. Con ayuda de Chrome ingenieros, logramos que los parches se implementaran tras una prueba de origen. (que permite que Chrome pruebe los cambios y obtenga comentarios de los desarrolladores antes de lanzar una API por completo).

Hemos tomado comentarios de la prueba de origen y de los demás miembros del W3C Web Performance Working Group e implementamos cambios en la API.

Ejemplo: un programador más productivo

Supongamos que debes realizar una gran cantidad de trabajo de bloqueo de visualización como generar lenguaje de marcado a partir de componentes, excluir números primos o dibujando un ícono giratorio de carga. Cada uno de ellos se divide en una tabla un elemento de trabajo. Con el patrón del programador, esbozamos cómo podríamos procesar nuestro trabajo en una función 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();
}

Si invocamos processWorkQueue() más adelante en una macrotarea nueva a través de setTimeout(), al navegador la capacidad de responder a la entrada (puede, ejecutar controladores de eventos antes de que se reanude el trabajo) y, al mismo tiempo, administrar la ejecución de sin interrupciones. Sin embargo, es posible que otros trabajos nos desprogramen por mucho tiempo que requiera el control del bucle de eventos o que obtenga hasta QUANTUM milisegundos adicionales de latencia de eventos.

Esto está bien, pero ¿podemos hacerlo mejor? ¡Por supuesto!

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

Mediante la introducción de una llamada a navigator.scheduling.isInputPending(), podemos hacer lo siguiente: responden a las entradas más rápido y nos aseguramos de que el bloqueo de la pantalla funcione se ejecuta sin interrupciones. Si no nos interesa manejar algo aparte de las aportaciones (p.ej., pintura) hasta que el trabajo esté completo, podemos aumentar la longitud de QUANTUM.

De forma predeterminada, “continuo” no se muestran desde isInputPending(). Estos incluyen a mousemove, pointermove y otros. Si te interesa rendir por también, no hay problema. Cuando proporcionas un objeto a isInputPending() con Se estableció includeContinuous en true, así que todo listo:

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

Eso es todo. Los frameworks como React están agregando compatibilidad con isInputPending() en su las bibliotecas de programación principales con una lógica similar. Con suerte, esto te permitirá desarrolladores que usan estos frameworks para beneficiarse de isInputPending() detrás de escena sin reescrituras significativas.

Rindar no siempre es malo

Cabe destacar que rendir menos no es la solución adecuada para todos los usos. para determinar si este es el caso. Existen muchos motivos para devolver el control al navegador además de procesar eventos de entrada, como realizar la renderización y ejecutar otras secuencias de comandos en la página.

Existen casos en los que el navegador no puede asignar correctamente el atributo pendiente de entrada. En particular, configurar clips y máscaras complejas para origen cruzado Los iframes pueden informar falsos negativos (p.ej., isInputPending() puede mostrarse de forma inesperada false cuando se orienta a estos marcos). Asegúrate de rendir con suficiente frecuencia si tu sitio requiere interacciones con submarcos estilizados.

Además, ten en cuenta otras páginas que comparten un bucle de eventos. En plataformas como como Chrome para Android, es bastante común que varios orígenes compartan un evento bucle. isInputPending() nunca mostrará true si la entrada se envía a un marco de origen cruzado y, por lo tanto, las páginas en segundo plano pueden interferir con el la capacidad de respuesta de las páginas en primer plano. Es posible que quieras reducir, posponer o ceder con más frecuencia cuando se realizan trabajos en segundo plano con la API de visibilidad de páginas.

Te recomendamos usar isInputPending() con discreción. Si no hay para bloquear a los usuarios y, luego, sé amable con los demás en el bucle de eventos. rinde con más frecuencia. Las tareas largas pueden ser dañinas.

Comentarios

  • Puedes dejar comentarios sobre la especificación en el is-input-pending.
  • Comunícate con @acomminos (uno de los autores de las especificaciones). en Twitter.

Conclusión

Nos complace que se lance isInputPending() y que los desarrolladores puedan para comenzar a usarlo hoy mismo. Esta API es la primera vez que Facebook compila una nueva API web y la llevó de la incubación de ideas a la propuesta de estándares, el envío en un navegador. Queremos agradecer a todas las personas que nos ayudaron a lograrlo y mencionar a todos los usuarios de Chrome que nos ayudaron a desarrollar esta idea y repartirla.

Foto hero de Will H McMahan en Retiro: