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

Información sobre Interaction to Next Paint (INP)

Acerca de este codelab

subjectÚltima actualización: ene 9, 2025
account_circleEscrito por Michal Mocny, Brendan Kenny

1. Introducción

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

Un diagrama que representa una interacción en el subproceso principal. El usuario ingresa datos mientras se ejecutan tareas de bloqueo. La entrada se retrasa hasta que se completan esas tareas, después de lo cual se ejecutan los objetos de escucha de eventos pointerup, mouseup y click, 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 entre las interacciones del usuario y el control de esas interacciones 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 de GitHub y ejecutar comandos de npm
  • Un editor de texto
  • Una versión reciente de Chrome para que funcionen todas las mediciones de interacción

2. Prepárate

Obtén y ejecuta el código

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

  1. Clona el repo en tu terminal: git clone https://github.com/GoogleChromeLabs/web-vitals-codelabs.git
  2. Navega 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 un contador de puntuación y un botón de 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: Es la puntuación actual del INP, que suele ser la peor interacción.
  • Interacción: Es la puntuación de la interacción más reciente.
  • FPS: Son los fotogramas por segundo del subproceso principal de la página.
  • Temporizador: Una animación de temporizador en ejecución para ayudar a visualizar el jank.

Las entradas de FPS y Timer no son necesarias para medir las interacciones. Se agregan solo para facilitar un poco 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 y Interaction cambian con cada incremento?

El INP mide cuánto tiempo transcurre desde el momento en que el usuario interactúa hasta que la página muestra la actualización renderizada.

3. Cómo medir las interacciones con las Herramientas para desarrolladores de Chrome

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

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

Captura de pantalla del panel Rendimiento de Herramientas para desarrolladores junto con 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 la línea de tiempo resultante, encontrarás un segmento de Interacciones. Para expandirlo, haz clic en el triángulo que se encuentra en el lado izquierdo.

Una demostración animada de cómo registrar una interacción con el panel Rendimiento de DevTools

Aparecerán dos interacciones. Acerca la segunda imagen desplazándote o manteniendo presionada la tecla W.

Captura de pantalla del panel Rendimiento de Herramientas para desarrolladores, con el cursor sobre la interacción en el panel y una sugerencia que muestra la duración breve de la interacción

Si colocas el cursor sobre la interacción, verás que fue rápida, que no se invirtió tiempo en la duración del procesamiento y que se invirtió una cantidad mínima de tiempo en la demora de entrada y la demora de presentación. La duración exacta dependerá de la velocidad de tu computadora.

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 de nuevo. Ahora las interacciones serán notablemente más lentas.

Registro de rendimiento

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

Una interacción de un segundo de duración en el panel Rendimiento

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

Cuando colocas el cursor sobre la interacción, observa que el tiempo se dedica casi por completo a la "Duración del procesamiento", que es el tiempo que se tarda en ejecutar las devoluciones de llamada del objeto de escucha de eventos. Dado que la llamada de bloqueo blockFor se encuentra completamente dentro del objeto de escucha de eventos, ahí es donde se consume el tiempo.

5. Experiment: processing duration

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

Actualiza la IU primero

¿Qué sucede si intercambias el orden de las llamadas a JS: primero actualizas la IU y, luego, bloqueas?

Ver 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 tomar un registro y examinar la interacción para ver si hubo alguna diferencia.

Objetos de escucha separados

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

Consulta 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 eventos de puntero o de teclado hasta eventos de desplazamiento, de enfoque o 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, ¿reemplazarías 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 la IU

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

Consulta el código completo: no_ui.html

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

6. Resultados del experimento de duración del procesamiento

Registro de rendimiento: Actualiza la IU primero

Ver el código completo: ui_first.html

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

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

Una interacción estática de un segundo de duración en el panel Rendimiento

Registro de rendimiento: Objetos de escucha separados

Consulta el código completo: two_click.html

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

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

De nuevo, no hay diferencias funcionales. La interacción sigue tardando un segundo completo.

Si acercas la interacción de clic, verás que, de hecho, se llaman dos funciones diferentes como resultado del evento click.

Como se esperaba, la primera, que actualiza la IU, se ejecuta muy rápido, mientras que la segunda tarda un segundo completo. Sin embargo, la suma de sus efectos genera la misma interacción lenta para el usuario final.

Una vista ampliada de la interacción de un segundo de duración en este ejemplo, en la que se muestra que la primera llamada a la función tarda menos de un milisegundo en completarse

Registro de rendimiento: Diferentes tipos de eventos

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

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

Estos resultados son muy similares. La interacción sigue durando un segundo completo. La única diferencia es que el objeto de escucha click más corto y que solo actualiza la IU ahora se ejecuta después del objeto de escucha pointerup de bloqueo.

Una vista ampliada de la interacción de un segundo de duración en este ejemplo, que muestra que el objeto de escucha de eventos de clic tarda menos de un milisegundo en completarse después del objeto de escucha de pointerup

Registro de rendimiento: No hay actualización de la IU

Consulta el código completo: no_ui.html

button.addEventListener('click', () => {
  blockFor
(1000);
 
// score.incrementAndUpdateUI();
});
  • La puntuación no se actualiza, pero la página sí.
  • Las animaciones, los efectos CSS, las acciones predeterminadas de los componentes web (entrada de formularios), la entrada de texto y el resaltado de texto se siguen actualizando.

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

Dado que el objeto de escucha de eventos bloqueó el subproceso principal durante un segundo, lo que impidió que se pintara la página, la interacción sigue tardando un segundo completo.

Si grabas un panel de rendimiento, se muestra la interacción prácticamente idéntica a las anteriores.

Una interacción estática de un segundo de duración en el panel Rendimiento

Conclusión

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

  • Esto incluye los objetos de escucha registrados desde diferentes secuencias de comandos y el código de framework o biblioteca que se ejecuta en los objetos de escucha, como una actualización de estado que activa la renderización de un componente.
  • No solo tu propio código, sino también todos los scripts de terceros.

Es un problema común.

Por último, el hecho de que tu código no active una pintura no significa que una pintura no esperará a que se completen los detectores de eventos lentos.

7. Experiment: input delay

¿Qué sucede con el código de ejecución prolongada fuera de los objetos de escucha de eventos? Por ejemplo:

  • Si tenías un <script> que se cargaba tarde y bloqueaba 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 si no se realiza ningún trabajo de bloqueo en la interacción.

Estos períodos de ejecución prolongada suelen llamarse 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 a la demora de entrada, no a la duración del procesamiento.

El panel Rendimiento de Herramientas para desarrolladores muestra una tarea de bloqueo de un segundo, una interacción que se produce a mitad de esa tarea y una interacción de 642 milisegundos, que se atribuye principalmente a la demora de entrada.

Ten en cuenta que no siempre afecta las interacciones. Si no haces clic cuando se ejecuta la tarea, es posible que tengas suerte. Estos estornudos "aleatorios" pueden ser una pesadilla para depurar cuando solo a veces causan problemas.

Una forma de detectarlas es medir las tareas largas (o Long Animation Frames) y el Tiempo total de bloqueo.

9. Presentación lenta

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

Bueno, actualizamos la página con efectos costosos.

Incluso si la actualización de la página se realiza rápidamente, es posible que el navegador aún deba trabajar mucho para renderizarla.

En el subproceso principal, haz lo siguiente:

  • Frameworks de IU que necesitan renderizar actualizaciones después de los cambios de estado
  • Los cambios en el DOM o la activación de muchos selectores de consultas CSS costosos pueden activar muchas operaciones de diseño, pintura y estilo.

Fuera del subproceso principal:

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

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

RenderingNG

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

  • Un sitio de SPA que vuelve a compilar todo el DOM después de hacer clic en un vínculo, sin detenerse para proporcionar comentarios visuales iniciales.
  • Una página de búsqueda que ofrece filtros de búsqueda complejos con una interfaz de usuario dinámica, pero ejecuta detectores costosos para hacerlo.
  • Un botón de activación del modo oscuro que activa el diseño y el diseño de toda la página

10. Experiment: presentation delay

requestAnimationFrame tiene un funcionamiento lento

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

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

Ver el código completo: presentation_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: presentation_delay.html

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

La interacción sigue durando un segundo, entonces, ¿qué sucedió?

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

Una interacción estática de un segundo de duración en el panel Rendimiento

Sin embargo, ten en cuenta dos aspectos:

  • Cuando coloques el cursor sobre el gráfico, verás que todo el tiempo de interacción ahora se dedica a la "demora en la presentación", ya que el bloqueo del subproceso principal se produce después de que regresa el objeto de escucha de eventos.
  • La raíz de la actividad del subproceso principal ya no es el evento de clic, sino "Se activó el fotograma de animación".

12. Diagnóstico de interacciones

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

Cuando las interacciones se extienden, no siempre está claro cuál es el problema. ¿Es lo siguiente?

  • ¿Hay retraso de entrada?
  • ¿Cuál es la duración del procesamiento de eventos?
  • ¿Retraso en la presentación?

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

  1. Navega por la Web como lo harías normalmente.
  2. Presta atención al registro de interacciones en la vista de métricas en tiempo real del panel Rendimiento de Herramientas para desarrolladores.
  3. Si ves una interacción con un rendimiento deficiente, intenta repetirla:
  • Si no puedes repetirlo, usa el registro de interacción para obtener estadísticas.
  • Si puedes repetirlo, graba un registro en el panel Rendimiento.

Todas las demoras

Intenta agregar un poco de todos 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 temporizadores o simplemente actualizar el estado global, ¿qué sucede cuando esos efectos finalmente actualizan la página?

La medición de la interacción se detiene siempre que se permita el procesamiento de la siguiente pintura después de una interacción, incluso si el navegador decide que no necesita una nueva actualización de procesamiento.

Para probar esto, sigue actualizando la IU desde el objeto de escucha de clics, pero ejecuta el trabajo de bloqueo desde el tiempo de espera.

Consulta el código completo: timeout_100.html

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

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

¿Qué hago?

14. Resultados del experimento de trabajo asíncrono

Consulta el 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 de duración que ahora ocurre más adelante en el registro

La interacción ahora es breve porque el subproceso principal está disponible inmediatamente después de que se actualiza la IU. La tarea de bloqueo prolongado sigue ejecutándose, solo que lo hace un tiempo después de la pintura, por lo que el usuario recibirá comentarios inmediatos de la IU.

Lección: Si no puedes quitarlo, al menos muévelo.

Métodos

¿Podemos hacerlo mejor que un setTimeout fijo de 100 milisegundos? Probablemente, aún queramos que el código se ejecute lo más rápido posible; de lo contrario, simplemente lo habríamos quitado.

Objetivo:

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

Algunas formas de lograrlo son las siguientes:

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

"requestPostAnimationFrame"

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

Ver el código completo: raf+task.html

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

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

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

Para mayor ergonomía, incluso puedes incluirlo en una promesa:

Consulta el 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 de frustración)

Mover el trabajo de bloqueo prolongado puede ayudar, pero esas tareas largas siguen bloqueando la página, lo que afecta las 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 propia si creaste tu propia variación para aplazar el trabajo en el último paso):

Consulta el código completo: timeout_100.html

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

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

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

Registro de rendimiento

Para cada clic, se pone en cola una tarea de un segundo de duración, lo que garantiza que el subproceso principal esté bloqueado durante un tiempo considerable.

Varias tareas de varios segundos en el subproceso principal, lo que provoca interacciones de hasta 800 ms

Cuando esas tareas largas se superponen con los clics nuevos que ingresan, se producen interacciones lentas, aunque el objeto de escucha de eventos se muestre casi de inmediato. Creamos la misma situación que en el experimento anterior con demoras de entrada. Solo que, esta vez, la demora de entrada no proviene de un setInterval, sino del trabajo que activaron los objetos de escucha de eventos anteriores.

Estrategias

Lo ideal sería quitar las tareas largas por completo.

  • Quita todo el código innecesario, en especial las secuencias de comandos.
  • Optimiza el código para evitar la ejecución de tareas prolongadas.
  • Anula el trabajo inactivo cuando llegan interacciones nuevas.

16. Estrategia 1: Reducción de rebotes

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

  • Usa setTimeout para retrasar el inicio de un trabajo costoso con un temporizador, tal vez de 500 a 1, 000 milisegundos.
  • Guarda el ID del temporizador cuando lo hagas.
  • Si llega una nueva interacción, cancela el temporizador anterior con clearTimeout.

Consulta el código completo: debounce.html

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

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

Registro de rendimiento

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

A pesar de los múltiples clics, solo se ejecuta una tarea de blockFor, que espera hasta que no haya habido clics durante un segundo completo antes de ejecutarse. Para las interacciones que se producen en ráfagas, como escribir en una entrada de texto o elementos objetivo que se espera que reciban varios clics rápidos, esta es una estrategia ideal para usar de forma predeterminada.

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

Aun así, existe la posibilidad de que se produzca otro clic justo después de que haya pasado el período de rebote, que se produzca en medio de esa tarea larga y que se convierta en una interacción muy lenta debido a la demora de entrada.

Lo ideal sería que, si una interacción se produce en medio de nuestra tarea, detengamos el trabajo ocupado para que las nuevas interacciones se manejen de inmediato. ¿Cómo podemos hacerlo?

Existen algunas APIs como isInputPending, pero generalmente es mejor dividir las tareas largas en fragmentos.

Muchos setTimeouts

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.

Varias interacciones, pero todo el trabajo programado se desglosó en muchas tareas más pequeñas

Volvemos a tener cinco segundos completos de trabajo para cinco clics, pero cada tarea de un segundo por clic se dividió en diez tareas de 100 milisegundos. Como resultado, incluso con varias interacciones que se superponen con esas tareas, ninguna interacción tiene una demora de entrada superior a 100 milisegundos. El navegador prioriza los objetos de escucha de eventos entrantes por sobre el trabajo de setTimeout, y las interacciones siguen siendo responsivas.

Esta estrategia funciona especialmente bien cuando se programan puntos de entrada separados, por ejemplo, si tienes un conjunto de funciones independientes que necesitas llamar en el tiempo de carga de la aplicación. Cargar secuencias de comandos y ejecutar todo en el momento de la evaluación de la secuencia de comandos puede ejecutar todo en una tarea gigante y larga de forma predeterminada.

Sin embargo, esta estrategia no funciona tan bien para separar el código estrechamente acoplado, como un bucle for que usa un estado compartido.

Ahora con yield()

Sin embargo, podemos aprovechar las funciones async y await modernas para agregar fácilmente "puntos de rendimiento" 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);
});

Como antes, el subproceso principal se cede después de un fragmento de trabajo, y el navegador puede responder a cualquier interacción entrante, pero ahora todo lo que se requiere es un await schedulerDotYield() en lugar de setTimeouts separados, lo que lo hace lo suficientemente ergonómico como para usarlo incluso en medio de un bucle for.

Ahora con AbortContoller()

Eso funcionó, pero cada interacción programa más trabajo, incluso si se produjeron interacciones nuevas que podrían haber cambiado el trabajo que se debe realizar.

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

Consulta el 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 se produce un clic, se inicia el bucle blockInPiecesYieldyAborty for, que realiza el trabajo necesario y, periódicamente, cede el subproceso principal para que el navegador siga respondiendo a las nuevas interacciones.

Cuando llega 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 la ejecución del primer bucle, notará que signal.aborted ahora es true y regresará de inmediato sin realizar más trabajo.

El trabajo del subproceso principal ahora se divide en muchas partes pequeñas, las interacciones son breves y el trabajo solo dura lo que debe durar.

18. Conclusión

Dividir todas las tareas largas permite que un sitio responda a las interacciones nuevas. Esto te permite proporcionar comentarios iniciales rápidamente y tomar decisiones, como abortar el trabajo en curso. A veces, eso significa programar los puntos de entrada como tareas separadas. A veces, eso significa agregar puntos de "rendimiento" donde sea conveniente.

Recuerda

  • El INP mide todas las interacciones.
  • Cada interacción se mide desde la entrada hasta la siguiente pintura, que es 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 de presentación todos afectan la capacidad de respuesta de la interacción.
  • Puedes medir el INP y los desgloses de las interacciones con Herramientas para desarrolladores fácilmente.

Estrategias

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

Más información