Información sobre la interacción con el siguiente procesamiento de imagen (INP)

1. Introducción

Una demostración interactiva y un codelab para aprender sobre Interaction to Next Paint (INP).

Un diagrama que muestra una interacción en el subproceso principal. El usuario realiza una entrada mientras bloquea la ejecución de las tareas. La entrada se retrasa hasta que se completan esas tareas. Luego, se ejecutan los objetos de escucha de eventos de punteroup, mouseup y clic, y luego se inicia el trabajo de renderización y pintura hasta que se presenta el siguiente fotograma.

Requisitos previos

Qué aprenderá

  • Cómo la interacción de las interacciones de los usuarios y tu manejo de estas afectan la capacidad de respuesta de la página.
  • Cómo reducir y eliminar las demoras para ofrecer una experiencia del usuario fluida

Requisitos

  • Una computadora con la capacidad de clonar código desde GitHub y ejecutar comandos npm.
  • Un editor de texto
  • Una versión reciente de Chrome para que funcionen todas las mediciones de interacción.

2. Prepárate

Cómo obtener y ejecutar el código

El código se encuentra en el repositorio de web-vitals-codelabs.

  1. Clona el repositorio en tu terminal: git clone https://github.com/GoogleChromeLabs/web-vitals-codelabs.git
  2. Pasa al directorio clonado: cd web-vitals-codelabs/understanding-inp.
  3. Instala las dependencias: npm ci
  4. Inicia el servidor web: npm run start
  5. Visita http://localhost:5173/understanding-inp/ en tu navegador

Descripción general de la app

En la parte superior de la página, se encuentran el contador Puntuación y el botón Incremento. ¡Una demostración clásica de reactividad y capacidad de respuesta!

Captura de pantalla de la app de demostración para este codelab

Debajo del botón, hay cuatro medidas:

  • INP: la puntuación de INP actual, que suele ser la peor interacción.
  • Interacción: La puntuación de la interacción más reciente
  • FPS: Los fotogramas por segundo del subproceso principal de la página.
  • Temporizador: Es una animación de temporizador en ejecución que ayuda a visualizar el bloqueo.

Las entradas de FPS y Timer no son necesarias para medir interacciones. Se agregan solo para facilitar la visualización de la capacidad de respuesta.

Probar

Intenta interactuar con el botón Increment y observa cómo aumenta la puntuación. ¿Los valores de INP e Interaction cambian con cada incremento?

INP mide el tiempo que transcurre desde el momento en que el usuario interactúa hasta que la página realmente le muestra la actualización procesada.

3. Medición de interacciones con las Herramientas para desarrolladores de Chrome

Abre las Herramientas para desarrolladores desde Más herramientas > En el menú Herramientas para desarrolladores, haz clic con el botón derecho en la página y selecciona Inspeccionar, o bien usa una combinación de teclas.

Cambia al panel Rendimiento, que usarás para medir las interacciones.

Captura de pantalla del panel Performance de Herramientas para desarrolladores junto a la app

A continuación, captura una interacción en el panel Rendimiento.

  1. Presiona Grabar.
  2. Interactúa con la página (presiona el botón Increment).
  3. Detén la grabación.

En el cronograma resultante, encontrarás un segmento de Interacciones. Para expandirlo, haz clic en el triángulo que aparece a la izquierda.

Una demostración animada de cómo grabar una interacción con el panel de rendimiento de Herramientas para desarrolladores

Aparecerán dos interacciones. Para acercar el segundo, desplázate o mantén presionada la tecla W.

Una captura de pantalla del panel Performance de Herramientas para desarrolladores, el cursor que se coloca sobre la interacción en el panel y un cuadro de información que muestra el breve tiempo de la interacción

Si colocas el cursor sobre la interacción, verás que la interacción fue rápida, no invirtió tiempo en la duración del procesamiento ni en retraso de entrada y retraso de presentación. La duración exacta dependerá de la velocidad de la máquina.

4. Objetos de escucha de eventos de larga duración

Abre el archivo index.js y quita el comentario de la función blockFor dentro del objeto de escucha de eventos.

Ver el código completo: click_block.html

button.addEventListener('click', () => {
  blockFor(1000);
  score.incrementAndUpdateUI();
});

Guarda el archivo. El servidor verá el cambio y actualizará la página por ti.

Intenta interactuar con la página nuevamente. Ahora las interacciones serán notablemente más lentas.

Seguimiento del rendimiento

Realiza otra grabación en el panel Rendimiento para ver cómo se ve aquí.

Una interacción de un segundo en el panel Rendimiento

Lo que antes era una interacción breve ahora tarda un segundo completo.

Cuando colocas el cursor sobre la interacción, observa que el tiempo se dedica casi por completo a "Duración del procesamiento", que es la cantidad de tiempo necesaria para ejecutar las devoluciones de llamada del objeto de escucha de eventos. Como la llamada de bloqueo a blockFor se encuentra completamente dentro del objeto de escucha de eventos, el tiempo pasa allí.

5. Experimento: duración del procesamiento

Prueba formas de reorganizar el trabajo del objeto de escucha de eventos para ver el efecto en el INP.

Primero actualiza la IU

¿Qué sucede si cambias el orden de las llamadas de js? Primero, actualiza la IU y, luego, realiza el bloqueo.

Consulta el código completo: ui_first.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
  blockFor(1000);
});

¿Notaste que la IU apareció antes? ¿El orden afecta las puntuaciones del INP?

Intenta hacer un seguimiento y examinar la interacción para ver si hubo alguna diferencia.

Objetos de escucha separados

¿Qué pasa si mueves el trabajo a un objeto de escucha de eventos separado? Actualiza la IU en un objeto de escucha de eventos y bloquea la página en un objeto de escucha independiente.

Ver el código completo: two_click.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
});

button.addEventListener('click', () => {
  blockFor(1000);
});

¿Cómo se ve ahora en el panel de rendimiento?

Diferentes tipos de eventos

La mayoría de las interacciones activarán muchos tipos de eventos, desde punteros o eventos clave hasta desplazamiento, enfoque/desenfoque y eventos sintéticos, como beforechange y beforeinput.

Muchas páginas reales tienen objetos de escucha para muchos eventos diferentes.

¿Qué sucede si cambias los tipos de eventos para los objetos de escucha de eventos? Por ejemplo, ¿reemplazas uno de los objetos de escucha de eventos click por pointerup o mouseup?

Consulta el código completo: diff_handlers.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
});

button.addEventListener('pointerup', () => {
  blockFor(1000);
});

Sin actualización de IU

¿Qué sucede si quitas la llamada para actualizar la IU del objeto de escucha de eventos?

Ver código completo: no_ui.html

button.addEventListener('click', () => {
  blockFor(1000);
  // score.incrementAndUpdateUI();
});

6. Procesando los resultados del experimento de duración

Seguimiento del rendimiento: Primero actualiza la IU

Consulta el código completo: ui_first.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
  blockFor(1000);
});

Si miras una grabación del panel Rendimiento cuando haces clic en el botón, puedes ver que los resultados no cambiaron. Si bien se activó una actualización de IU antes del código de bloqueo, el navegador no actualizó lo que se pintó en la pantalla hasta después de que se completara el objeto de escucha de eventos, lo que significa que la interacción aún tardó un poco más de un segundo en completarse.

Una interacción aún de un segundo en el panel Rendimiento

Seguimiento del rendimiento: objetos de escucha separados

Ver el código completo: two_click.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
});

button.addEventListener('click', () => {
  blockFor(1000);
});

Una vez más, funcionalmente no hay diferencia. La interacción aún tarda un segundo completo.

Si te acercas mucho a la interacción del clic, verás que se llama a dos funciones diferentes como resultado del evento click.

Como era de esperar, la primera (actualizar la IU) se ejecuta increíblemente rápido, mientras que la segunda tarda un segundo completo. Sin embargo, la suma de sus efectos da como resultado la misma interacción lenta para el usuario final.

Una mirada más cercana a la interacción de un segundo de este ejemplo en la que se muestra que la primera llamada a función tarda menos de un milisegundo en completarse.

Seguimiento del rendimiento: diferentes tipos de eventos

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
});

button.addEventListener('pointerup', () => {
  blockFor(1000);
});

Estos resultados son muy similares. La interacción aún es de un segundo completo; La única diferencia es que el objeto de escucha click más corto solo para actualizaciones de IU ahora se ejecuta después del objeto de escucha pointerup que genera el bloqueo.

Una mirada más cercana a la interacción de un segundo de este ejemplo, en la que se muestra el objeto de escucha de eventos de clics que tarda menos de un milisegundo en completarse, después del objeto de escucha de punteroup.

Seguimiento de rendimiento: sin actualización de la IU

Ver código completo: no_ui.html

button.addEventListener('click', () => {
  blockFor(1000);
  // score.incrementAndUpdateUI();
});
  • No se actualiza la puntuación, pero la página sigue funcionando.
  • Las animaciones, los efectos CSS, las acciones de componentes web predeterminadas (entrada de formulario), la entrada de texto y los elementos destacados de texto siguen actualizándose.

En este caso, el botón pasa a un estado activo y vuelve a un estado activo cuando se hace clic en él, lo que requiere que el navegador pinte, lo que significa que aún hay un INP.

Como el objeto de escucha de eventos bloqueó el subproceso principal por un segundo y impidió que se pinte la página, la interacción aún tarda un segundo completo.

Cuando grabas el panel Rendimiento, la interacción es prácticamente idéntica a la de antes.

Una interacción aún de un segundo en el panel Rendimiento

Comida para llevar

Cualquier código que se ejecute en cualquier objeto de escucha de eventos retrasará la interacción.

  • Eso incluye objetos de escucha registrados a partir de diferentes secuencias de comandos y código de framework o biblioteca que se ejecuta en objetos de escucha, como una actualización de estado que activa la renderización de un componente.
  • No solo su propio código, sino también todas las secuencias de comandos de terceros.

Es un problema común.

Por último, el hecho de que tu código no active una pintura no significa que esta no estará esperando a que se completen los objetos de escucha de eventos lentos.

7. Experimento: retraso de entrada

¿Qué ocurre con el código de larga duración fuera de los objetos de escucha de eventos? Por ejemplo:

  • Si tenías una <script> de carga tardía que bloqueó la página de forma aleatoria durante la carga.
  • ¿Una llamada a la API, como setInterval, que bloquea la página de forma periódica?

Intenta quitar el blockFor del objeto de escucha de eventos y agregarlo a un setInterval():

Consulta el código completo: input_delay.html

setInterval(() => {
  blockFor(1000);
}, 3000);


button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
});

¿Qué ocurre entonces?

8. Resultados del experimento de retraso de entrada

Consulta el código completo: input_delay.html

setInterval(() => {
  blockFor(1000);
}, 3000);


button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
});

Grabar un clic en un botón que ocurre mientras se ejecuta la tarea de bloqueo de setInterval genera una interacción de larga duración, incluso cuando no se realiza trabajo de bloqueo en la interacción en sí.

Estos períodos de larga duración a menudo se denominan tareas largas.

Si colocas el cursor sobre la interacción en Herramientas para desarrolladores, podrás ver que el tiempo de interacción ahora se atribuye principalmente al retraso de entrada, no a la duración del procesamiento.

El panel Performance de Herramientas para desarrolladores muestra una tarea de bloqueo de un segundo, una interacción que llega parcialmente a esa tarea y una interacción de 642 milisegundos, atribuida principalmente al retraso de entrada.

Ten en cuenta que no siempre afecta las interacciones. Si no haces clic cuando se está ejecutando la tarea, es posible que tengas suerte. Es algo tan "aleatorio" estornudos puede ser una pesadilla cuando solo causan problemas a veces.

Una forma de hacerlo es medir las tareas largas (o Fotogramas de animación largos) y el Tiempo de bloqueo total.

9. Presentación lenta

Hasta ahora, analizamos el rendimiento de JavaScript mediante la demora de entrada o los objetos de escucha de eventos, pero ¿qué más afecta a la renderización de la siguiente pintura?

Actualizar la página con efectos costosos.

Incluso si la página se actualiza rápidamente, es posible que el navegador deba hacer todo lo posible para procesarlas.

En el subproceso principal:

  • Frameworks de la IU que deben renderizar actualizaciones después de los cambios de estado
  • Los cambios de DOM o la activación de muchos selectores de consultas de CSS costosos pueden desencadenar muchos estilos, diseños y pinturas.

Fuera del subproceso principal:

  • Cómo usar CSS para potenciar los efectos de la GPU
  • Agregar imágenes de alta resolución muy grandes
  • Uso de SVG/Canvas para dibujar escenas complejas

Boceto de los diferentes elementos de renderización en la Web

RenderingNG

Estos son algunos ejemplos que se encuentran comúnmente en la Web:

  • Un sitio SPA que reconstruye todo el DOM después de hacer clic en un vínculo, sin hacer una pausa para proporcionar una primera respuesta visual.
  • Una página de búsqueda que ofrece filtros de búsqueda complejos con una interfaz de usuario dinámica, pero ejecuta objetos de escucha costosos para hacerlo.
  • Un botón de activación del modo oscuro que activa el estilo o diseño de toda la página

10. Experimento: retraso en la presentación

requestAnimationFrame tiene un funcionamiento lento

Simulemos una larga demora de presentación con la API de requestAnimationFrame().

Mueve la llamada a blockFor a una devolución de llamada requestAnimationFrame para que se ejecute después de que muestre el objeto de escucha de eventos:

Ver el código completo: presentación_delay.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
  requestAnimationFrame(() => {
    blockFor(1000);
  });
});

¿Qué ocurre entonces?

11. Resultados del experimento de retraso en la presentación

Ver el código completo: presentación_delay.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
  requestAnimationFrame(() => {
    blockFor(1000);
  });
});

La interacción dura un segundo, entonces, ¿qué pasó?

requestAnimationFrame solicita una devolución de llamada antes de la siguiente pintura. Dado que INP mide el tiempo desde la interacción hasta la siguiente pintura, blockFor(1000) en requestAnimationFrame continúa bloqueando la siguiente pintura durante un segundo completo.

Una interacción aún de un segundo en el panel Rendimiento

Sin embargo, ten en cuenta dos cosas:

  • Cuando coloques el cursor sobre un elemento, verás todo el tiempo de interacción que se invierte en "demora en la presentación". ya que el bloqueo del subproceso principal se produce después de que vuelve el objeto de escucha de eventos.
  • La raíz de la actividad del subproceso principal ya no es el evento de clic, sino "Animation Frame Fired".

12. Cómo diagnosticar interacciones

En esta página de prueba, la capacidad de respuesta es muy visual, con las puntuaciones, los cronómetros y la IU de contador, pero cuando se prueba la página promedio, es más sutil.

Cuando las interacciones se prolongan, no siempre está claro cuál es la causa. Es:

  • ¿Retraso en la entrada?
  • ¿Duración del procesamiento de eventos?
  • ¿Se retrasa la presentación?

En cualquier página que desees, puedes usar Herramientas para desarrolladores para medir la capacidad de respuesta. Para adoptar el hábito, prueba este flujo:

  1. Navega por la Web como lo harías normalmente.
  2. Opcional: Deja abierta la consola de Herramientas para desarrolladores mientras la extensión de Métricas web registra las interacciones.
  3. Si observas una interacción que tiene un rendimiento bajo, intenta repetirla:
  • Si no puedes repetirlo, usa los registros de la consola para obtener estadísticas.
  • Si puedes repetirlo, grábalo en el panel de rendimiento.

Todos los retrasos

Intenta agregar algunos de estos problemas a la página:

Consulta el código completo: all_the_things.html

setInterval(() => {
  blockFor(1000);
}, 3000);

button.addEventListener('click', () => {
  blockFor(1000);
  score.incrementAndUpdateUI();

  requestAnimationFrame(() => {
    blockFor(1000);
  });
});

Luego, usa la consola y el panel de rendimiento para diagnosticar los problemas.

13. Experimento: trabajo asíncrono

Dado que puedes iniciar efectos no visuales dentro de las interacciones, como realizar solicitudes de red, iniciar cronómetros o solo actualizar el estado global, ¿qué sucede cuando estas actualizan la página finalmente?

Siempre y cuando se permita renderizar el siguiente procesamiento de imagen después de una interacción, incluso si el navegador decide que en realidad no necesita una nueva actualización de renderización, se detendrá la medición de interacciones.

Para probar esto, continúa actualizando la IU desde el objeto de escucha de clics, pero ejecuta la tarea de bloqueo desde el tiempo de espera.

Ver código completo: timeout_100.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();

  setTimeout(() => {
    blockFor(1000);
  }, 100);
});

¿Qué sucede ahora?

14. Resultados del experimento de trabajo asíncrono

Ver código completo: timeout_100.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();

  setTimeout(() => {
    blockFor(1000);
  }, 100);
});

Una interacción de 27 milisegundos con una tarea de un segundo que ahora ocurre más adelante en el seguimiento

Ahora, la interacción es corta porque el subproceso principal está disponible inmediatamente después de que se actualiza la IU. La tarea de bloqueo prolongada aún se ejecuta, solo después de la pintura, por lo que el usuario obtendrá comentarios inmediatos de la IU.

Lección: si no puedes eliminarlo, al menos muévelo.

Métodos

¿Podemos hacer mejor que un setTimeout fijo de 100 milisegundos? Es probable que aún queramos que el código se ejecute lo más rápido posible; de lo contrario, deberíamos haberlo eliminado.

Objetivo:

  • La interacción se ejecutará incrementAndUpdateUI().
  • blockFor() se ejecutará lo antes posible, pero no bloqueará la siguiente pintura.
  • Esto da como resultado un comportamiento predecible sin “tiempos de espera mágicos”.

Estas son algunas formas de lograrlo:

  • setTimeout(0)
  • Promise.then()
  • requestAnimationFrame
  • requestIdleCallback
  • scheduler.postTask()

&quot;requestPostAnimationFrame&quot;

A diferencia de requestAnimationFrame por sí solo (que intentará ejecutarse antes de la siguiente pintura y, por lo general, hará que la interacción sea lenta), requestAnimationFrame + setTimeout permiten un polyfill simple para requestPostAnimationFrame, que ejecuta la devolución de llamada después de la siguiente pintura.

Ver código completo: raf+task.html

function afterNextPaint(callback) {
  requestAnimationFrame(() => {
    setTimeout(callback, 0);
  });
}

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();

  afterNextPaint(() => {
    blockFor(1000);
  });
});

En cuanto a la ergonomía, puedes incluirla en una promesa:

Ver código completo: raf+task2.html

async function nextPaint() {
  return new Promise(resolve => afterNextPaint(resolve));
}

button.addEventListener('click', async () => {
  score.incrementAndUpdateUI();

  await nextPaint();
  blockFor(1000);
});

15. Varias interacciones (y clics rápidos)

Mover el bloqueo largo puede ayudar, pero esas tareas largas siguen bloqueando la página y afectando a interacciones futuras, así como muchas otras animaciones y actualizaciones de la página.

Vuelve a probar la versión de trabajo de bloqueo asíncrono de la página (o la tuya si se te ocurre tu propia variación sobre el aplazamiento del trabajo en el último paso):

Ver código completo: timeout_100.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();

  setTimeout(() => {
    blockFor(1000);
  }, 100);
});

¿Qué sucede si haces clic varias veces rápidamente?

Seguimiento del rendimiento

Por cada clic, hay una tarea de un segundo en cola, lo que garantiza que el subproceso principal esté bloqueado durante un período considerable.

Varias tareas de segundos de duración en el subproceso principal que provocan interacciones lentas como de hasta 800 ms.

Cuando esas tareas largas se superponen con nuevos clics entrantes, se generan interacciones lentas, a pesar de que el objeto de escucha de eventos mismo devuelve resultados casi de inmediato. Creamos la misma situación que en el experimento anterior con retrasos en las entradas. Solo que esta vez, el retraso de entrada no proviene de un setInterval, sino del trabajo activado por objetos de escucha de eventos anteriores.

Estrategias

Idealmente, queremos quitar las tareas largas por completo.

  • Quita por completo el código innecesario, especialmente las secuencias de comandos.
  • Optimiza el código para evitar ejecutar tareas largas.
  • Anula el trabajo obsoleto cuando lleguen nuevas interacciones.

16. Estrategia 1: Anulación de rebote

Una estrategia clásica. Cuando las interacciones llegan en una sucesión rápida y los efectos de red o de procesamiento son costosos, retrasa el inicio del trabajo a propósito para que puedas cancelarlo y reiniciarlo. Este patrón es útil para interfaces de usuario como los campos de autocompletado.

  • Usa setTimeout para retrasar el inicio de trabajos costosos, con un temporizador, aproximadamente entre 500 y 1,000 milisegundos.
  • Guarda el ID del temporizador cuando lo hagas.
  • Si llega una nueva interacción, cancela el temporizador anterior con clearTimeout.

Ver el código completo: debounce.html

let timer;
button.addEventListener('click', () => {
  score.incrementAndUpdateUI();

  if (timer) {
    clearTimeout(timer);
  }
  timer = setTimeout(() => {
    blockFor(1000);
  }, 1000);
});

Seguimiento del rendimiento

Varias interacciones, pero solo una única tarea larga de trabajo como resultado de todas ellas

A pesar de los múltiples clics, solo se ejecuta una tarea blockFor, que espera a que no haya ningún clic durante un segundo completo antes de ejecutarse. En el caso de las interacciones que se producen en aumentos de actividad, como escribir una entrada de texto o objetivos de elementos que se espera que obtengan varios clics rápidos, esta es una estrategia ideal para usar de forma predeterminada.

17. Estrategia 2: Interrumpir el trabajo de larga duración

Todavía existe la mala suerte de que recibas otro clic justo después de que haya transcurrido el período de espera, que se realice en medio de esa larga tarea y se convierta en una interacción muy lenta debido a la demora en las entradas.

Idealmente, si una interacción llega en medio de nuestra tarea, queremos pausar nuestro trabajo pesado para que cualquier interacción nueva se controle de inmediato. ¿Cómo podemos hacerlo?

Hay algunas APIs, como isInputPending, pero en general, es mejor dividir las tareas largas en fragmentos.

Muchos setTimeout

Primer intento: haz algo simple.

Ver el código completo: small_tasks.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();

  requestAnimationFrame(() => {
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
  });
});

Esto funciona permitiendo que el navegador programe cada tarea de forma individual, y la entrada puede tener mayor prioridad.

Múltiples interacciones, pero todo el trabajo programado se dividió en muchas tareas más pequeñas

Volvimos a dedicar cinco segundos de trabajo a realizar cinco clics, pero cada tarea de un segundo por clic se dividió en diez tareas de 100 milisegundos. Como resultado, incluso con múltiples interacciones superpuestas con esas tareas, ninguna interacción tiene un retraso de entrada superior a 100 milisegundos. El navegador prioriza los objetos de escucha de eventos entrantes sobre el trabajo de setTimeout, y las interacciones siguen siendo responsivas.

Esta estrategia funciona especialmente bien cuando se programan puntos de entrada separados, como cuando tienes un conjunto de funciones independientes a las que necesitas llamar en el tiempo de carga de la aplicación. El solo hecho de cargar las secuencias de comandos y ejecutar todo al momento de la evaluación de las secuencias de comandos puede ejecutar todo en una enorme tarea larga de forma predeterminada.

Sin embargo, esta estrategia no funciona tan bien para dividir el código con acoplamiento alto, como un bucle for que usa el estado compartido.

Ahora con yield()

Sin embargo, podemos aprovechar async y await modernos para agregar "puntos de rendimiento" con facilidad. a cualquier función de JavaScript.

Por ejemplo:

Ver el código completo: yieldy.html

// Polyfill for scheduler.yield()
async function schedulerDotYield() {
  return new Promise(resolve => {
    setTimeout(resolve, 0);
  });
}

async function blockInPiecesYieldy(ms) {
  const ms_per_part = 10;
  const parts = ms / ms_per_part;
  for (let i = 0; i < parts; i++) {
    await schedulerDotYield();

    blockFor(ms_per_part);
  }
}

button.addEventListener('click', async () => {
  score.incrementAndUpdateUI();
  await blockInPiecesYieldy(1000);
});

Al igual que antes, se produce el subproceso principal después de una parte del trabajo, y el navegador puede responder a cualquier interacción entrante, pero ahora todo lo que se necesita es un await schedulerDotYield() en lugar de setTimeout separados, lo que hace que sea lo suficientemente ergonómico para usarlo incluso en medio de un bucle for.

Ahora con AbortContoller()

Eso funcionó, pero cada interacción programa más trabajo, incluso si han surgido nuevas interacciones y es posible que hayan cambiado el trabajo que se debe hacer.

Con la estrategia de rebote, cancelamos el tiempo de espera anterior con cada interacción nueva. ¿Podemos hacer algo similar aquí? Una forma de hacerlo es usar un AbortController():

Ver código completo: aborty.html

// Polyfill for scheduler.yield()
async function schedulerDotYield() {
  return new Promise(resolve => {
    setTimeout(resolve, 0);
  });
}

async function blockInPiecesYieldyAborty(ms, signal) {
  const parts = ms / 10;
  for (let i = 0; i < parts; i++) {
    // If AbortController has been asked to stop, abandon the current loop.
    if (signal.aborted) return;

    await schedulerDotYield();

    blockFor(10);
  }
}

let abortController = new AbortController();

button.addEventListener('click', async () => {
  score.incrementAndUpdateUI();

  abortController.abort();
  abortController = new AbortController();

  await blockInPiecesYieldyAborty(1000, abortController.signal);
});

Cuando ingresa un clic, se inicia el bucle for de blockInPiecesYieldyAborty que hace todo el trabajo necesario mientras procesa periódicamente el subproceso principal para que el navegador siga respondiendo a las interacciones nuevas.

Cuando ingresa un segundo clic, el primer bucle se marca como cancelado con AbortController y se inicia un nuevo bucle blockInPiecesYieldyAborty. La próxima vez que se programe el primer bucle para volver a ejecutarse, nota que signal.aborted ahora es true y se muestra inmediatamente sin realizar más trabajo.

Ahora el trabajo del subproceso principal es muy pequeño, las interacciones son cortas y el trabajo solo dura el tiempo que sea necesario.

18. Conclusión

Separar todas las tareas largas permite que un sitio responda a las interacciones nuevas. Esto te permite proporcionar comentarios iniciales rápidamente y también te permite tomar decisiones como anular el trabajo en curso. A veces, eso significa programar los puntos de entrada como tareas independientes. A veces, esto significa agregar "rendimiento" puntos cuando sea conveniente.

Recuerda

  • INP mide todas las interacciones.
  • Cada interacción se mide desde la entrada hasta el siguiente procesamiento de imagen, es decir, la forma en que el usuario ve la capacidad de respuesta.
  • El retraso de entrada, la duración del procesamiento de eventos y el retraso en la presentación todos afectan la capacidad de respuesta a la interacción.
  • Puede medir fácilmente los desgloses de INP y de interacción con Herramientas para desarrolladores.

Estrategias

  • No tengas código de larga duración (tareas largas) en tus páginas.
  • Quita el código innecesario de los objetos de escucha de eventos hasta después de la siguiente pintura.
  • Asegúrate de que la actualización de renderización sea eficiente para el navegador.

Más información