La creación de sitios web que respondan rápidamente a las entradas de los usuarios ha sido uno de los aspectos más desafiantes del rendimiento web. El equipo de Chrome ha trabajado arduamente para ayudar a los desarrolladores web a reunirse. Justo este año, se anunció que la métrica Interacción a la siguiente pintura (INP) pasaría del estado experimental a pendiente. Ahora se espera que reemplace el Retraso de primera entrada (FID) como Métrica web esencial en marzo de 2024.
En un esfuerzo continuo por ofrecer nuevas APIs que ayuden a los desarrolladores web a hacer que sus sitios web sean lo más ágiles posible, el equipo de Chrome está ejecutando una prueba de origen para scheduler.yield
a partir de la versión 115. scheduler.yield
es una nueva incorporación propuesta para la API del programador que ofrece una manera más fácil y eficaz de devolverle el control al subproceso principal que los métodos en los que tradicionalmente se confía.
Rendimiento
JavaScript usa el modelo de ejecución hasta finalización para procesar las tareas. Esto significa que, cuando una tarea se ejecuta en el subproceso principal, se ejecuta el tiempo necesario para completarse. Cuando se completa una tarea, el control se genera de nuevo en el subproceso principal, lo que permite que este procese la siguiente tarea de la cola.
Además de los casos extremos en los que una tarea nunca finaliza, como un bucle infinito, por ejemplo, el rendimiento es un aspecto inevitable de la lógica de programación de tareas de JavaScript. Sucederá, es solo cuestión de cuándo, y antes es mejor que después. Cuando las tareas tardan demasiado en ejecutarse (más de 50 milisegundos, para ser exactos), se consideran tareas largas.
Las tareas largas son una fuente de respuesta deficiente de la página porque retrasan la capacidad del navegador para responder a la entrada del usuario. Cuanto más a menudo se producen las tareas largas, y mientras más se ejecuten, más probable será que los usuarios tengan la impresión de que la página es lenta o incluso que está rota.
Sin embargo, el hecho de que tu código inicie una tarea en el navegador no significa que tengas que esperar hasta que la tarea se complete antes de devolver el control al subproceso principal. Para mejorar la capacidad de respuesta a la entrada del usuario en una página, se genera explícitamente una tarea, lo que divide la tarea para que se complete en la siguiente oportunidad disponible. Esto permite que otras tareas obtengan tiempo en el subproceso principal más rápido que si tuvieran que esperar a que finalicen las tareas largas.
Cuando cedas de forma explícita, le dices al navegador "Hola, entiendo que el trabajo que estoy por hacer podría llevar tiempo y no quiero que tengas que hacer todo ese trabajo antes de responder a la entrada del usuario u otras tareas que también podrían ser importantes". Es una herramienta valiosa dentro de la caja de herramientas de un desarrollador que puede contribuir en gran medida a mejorar la experiencia del usuario.
El problema con las estrategias de rendimiento actuales
Un método común de rendimiento de usa setTimeout
con un valor de tiempo de espera de 0
. Esto funciona porque la devolución de llamada que se pasa a setTimeout
moverá el trabajo restante a una tarea independiente que se pondrá en cola para la ejecución posterior. En lugar de esperar a que el navegador rinda por su cuenta, indicas “dividamos esta gran parte del trabajo en partes más pequeñas”.
Sin embargo, generar con setTimeout
tiene un efecto secundario potencialmente no deseado: el trabajo que viene después del punto de productividad irá al final de la lista de tareas en cola. Las tareas programadas por interacciones de usuarios seguirán yendo al principio de la cola como deberían, pero el trabajo restante que querías hacer después de mostrar datos de forma explícita podría terminar con un retraso aún mayor por otras tareas de fuentes rivales que se pusieron en cola antes que él.
Para ver esto en acción, prueba esta demostración de Glitch o experimenta con ella en la versión incorporada que aparece a continuación. La demostración consta de algunos botones en los que puedes hacer clic y un cuadro debajo de ellos que registra cuando se ejecutan las tareas. Cuando se encuentre en la página, realice las siguientes acciones:
- Haz clic en el botón superior etiquetado como Ejecutar tareas periódicamente, el cual programará las tareas de bloqueo para que se ejecuten de vez en cuando. Cuando hagas clic en este botón, el registro de tareas se propagará con varios mensajes que dicen Tarea de bloqueo de ejecución con
setInterval
. - A continuación, haz clic en el botón Run loop, que rinde con
setTimeout
en cada iteración.
Notarás que el cuadro en la parte inferior de la demostración se leerá de la siguiente manera:
Processing loop item 1
Processing loop item 2
Ran blocking task via setInterval
Processing loop item 3
Ran blocking task via setInterval
Processing loop item 4
Ran blocking task via setInterval
Processing loop item 5
Ran blocking task via setInterval
Ran blocking task via setInterval
En este resultado, se muestra el “final de la lista de tareas en cola” Comportamiento que se produce cuando se procesa el rendimiento con setTimeout
El bucle que ejecuta procesa cinco elementos y rinde con setTimeout
después de que se procesa cada uno.
Esto ilustra un problema común en la Web: no es inusual que una secuencia de comandos (especialmente una de terceros) registre una función de temporizador que ejecuta trabajo en algún intervalo. El “fin de la lista de tareas en cola” El comportamiento que se genera con el rendimiento con setTimeout
significa que el trabajo de otras fuentes de tareas se puede poner en cola antes del trabajo restante que el bucle tiene que hacer después de ceder.
Según tu aplicación, este puede o no ser un resultado conveniente. Sin embargo, en muchos casos, este comportamiento es el motivo por el que los desarrolladores pueden sentirse reacios a ceder el control del subproceso principal con tanta facilidad. Generar un rendimiento es bueno porque las interacciones del usuario tienen la oportunidad de ejecutarse más rápido, pero también permite que otras tareas de interacción que no sean del usuario obtengan tiempo en el subproceso principal. Es un verdadero problema, pero scheduler.yield
puede ayudar a resolverlo.
Ingresa scheduler.yield
.
scheduler.yield
está disponible detrás de una marca como función experimental de la plataforma web desde la versión 115 de Chrome. Una pregunta que podrías tener es "¿por qué necesito una función especial para generar rendimiento cuando setTimeout
ya lo hace?".
Vale la pena señalar que el rendimiento no era un objetivo de diseño de setTimeout
, sino un buen efecto secundario al programar una devolución de llamada para que se ejecute más adelante, incluso con un valor de tiempo de espera de 0
especificado. Sin embargo, es más importante recordar que el procesamiento con setTimeout
envía el trabajo restante al atrás de la lista de tareas en cola. De forma predeterminada, scheduler.yield
envía el trabajo restante al primer plano de la cola. Esto significa que el trabajo que deseas reanudar inmediatamente después de ceder no se convertirá en tareas de otras fuentes (excepto las interacciones de los usuarios).
scheduler.yield
es una función que cede al subproceso principal y muestra un Promise
cuando se lo llama. Esto significa que puedes await
en una función async
:
async function yieldy () {
// Do some work...
// ...
// Yield!
await scheduler.yield();
// Do some more work...
// ...
}
Para ver scheduler.yield
en acción, haz lo siguiente:
- Navega a
chrome://flags
. - Habilita el experimento Funciones experimentales de la plataforma web. Es posible que debas reiniciar Chrome después de hacer esto.
- Navega a la página de demostración o usa su versión incorporada debajo de esta lista.
- Haz clic en el botón superior con la etiqueta Ejecutar tareas de forma periódica.
- Por último, haz clic en el botón Run loop, que rinde con
scheduler.yield
en cada iteración.
El resultado en el cuadro ubicado en la parte inferior de la página será similar al siguiente:
Processing loop item 1
Processing loop item 2
Processing loop item 3
Processing loop item 4
Processing loop item 5
Ran blocking task via setInterval
Ran blocking task via setInterval
Ran blocking task via setInterval
Ran blocking task via setInterval
Ran blocking task via setInterval
A diferencia de la demostración que genera con setTimeout
, puedes ver que el bucle, aunque rinde después de cada iteración, no envía el trabajo restante al fondo de la cola, sino al principio de ella. Esto te da lo mejor de ambos mundos: puedes mejorar la capacidad de respuesta de la entrada en tu sitio web, pero también asegurarte de que el trabajo que querías terminar después de generar no se retrase.
¡Pruébalo!
Si scheduler.yield
te parece interesante y quieres probarlo, puedes hacerlo de dos maneras a partir de la versión 115 de Chrome:
- Si quieres experimentar con
scheduler.yield
de forma local, escribe e ingresachrome://flags
en la barra de direcciones de Chrome y selecciona Habilitar en el menú desplegable de la sección Funciones experimentales de la plataforma web. Esta acción hará quescheduler.yield
(y cualquier otra función experimental) esté disponible solo en tu instancia de Chrome. - Si quieres habilitar
scheduler.yield
para usuarios reales de Chromium en un origen de acceso público, deberás registrarte en la prueba de origen descheduler.yield
. Esto te permite experimentar de forma segura con las funciones propuestas durante un período determinado y brinda al equipo de Chrome información valiosa sobre cómo se usan esas funciones en el campo. Para obtener más información sobre cómo funcionan las pruebas de origen, lee esta guía.
La manera en que uses scheduler.yield
(y, al mismo tiempo, admitas navegadores que no lo implementan) dependerá de cuáles sean tus objetivos. Puedes usar el polyfill oficial. El polyfill es útil si la siguiente situación se aplica a tu caso:
- Ya estás usando
scheduler.postTask
en tu aplicación para programar tareas. - Quieres poder establecer tareas y obtener prioridades.
- Deseas poder cancelar o volver a priorizar tareas a través de la clase
TaskController
que ofrece la API descheduler.postTask
.
Si no se describe tu situación, es posible que el polyfill no sea adecuado para ti. En ese caso, puedes revertir tu propio resguardo de dos maneras. El primer enfoque usa scheduler.yield
si está disponible, pero recurre a setTimeout
si no lo está:
// A function for shimming scheduler.yield and setTimeout:
function yieldToMain () {
// Use scheduler.yield if it exists:
if ('scheduler' in window && 'yield' in scheduler) {
return scheduler.yield();
}
// Fall back to setTimeout:
return new Promise(resolve => {
setTimeout(resolve, 0);
});
}
// Example usage:
async function doWork () {
// Do some work:
// ...
await yieldToMain();
// Do some other work:
// ...
}
Esto puede funcionar, pero como te imaginarás, los navegadores que no sean compatibles con scheduler.yield
mostrarán el contenido sin "primera fila". el comportamiento de los usuarios. Si eso significa que prefieres no ceder, puedes probar otro enfoque que use scheduler.yield
si está disponible, pero que no rinde en absoluto si no lo está:
// A function for shimming scheduler.yield with no fallback:
function yieldToMain () {
// Use scheduler.yield if it exists:
if ('scheduler' in window && 'yield' in scheduler) {
return scheduler.yield();
}
// Fall back to nothing:
return;
}
// Example usage:
async function doWork () {
// Do some work:
// ...
await yieldToMain();
// Do some other work:
// ...
}
scheduler.yield
es una incorporación emocionante a la API del programador, que permitirá que los desarrolladores mejoren la capacidad de respuesta con mayor facilidad que las estrategias de rendimiento actuales. Si scheduler.yield
te parece una API útil, participa en nuestra investigación para ayudarnos a mejorarla y envíanos comentarios sobre cómo se puede mejorar.
Hero image de Unsplash, de Jonathan Allison.