Video Summary and Transcription
Esta charla cubre el bucle de eventos, la cola de microtareas y proporciona una demostración en vivo. JavaScript es de un solo hilo pero puede realizar tareas que solo un entorno multihilo puede. El bucle de eventos consta de una pila de llamadas y una cola de microtareas, que permiten que JavaScript ejecute operaciones no bloqueantes. Aprovechar la cola de microtareas puede llevar a mejoras significativas en el rendimiento de las aplicaciones, como React. Sin embargo, es importante usarlo correctamente para evitar problemas como bucles infinitos.
1. Introducción al bucle de eventos y la cola de microtareas
Soy Michael Di Brisco, conocido como Kadevan, un desarrollador senior en Jointly y un contribuyente de código abierto. Hoy, cubriremos tres temas principales: el bucle de eventos, la cola de microtareas y una demostración en vivo. JavaScript es tanto de un solo hilo como no. Exploraremos esto más a fondo. La especificación ECMAScript no permite la multitarea.
Espero que estén disfrutando de Londres hasta ahora. Está lloviendo, pero es una ciudad encantadora. Entonces, como dijo Jessica, soy Michael Di Brisco, conocido en la web como Kadevan, soy un desarrollador senior en Jointly y soy un padre junior en casa y también soy un contribuyente de código abierto. Pero no soy la parte principal de esta charla, así que primero que nada, disculpen mi inglés y soy italiano, así que sí, lo que sea.
Y vayamos a nuestra tabla de contenidos. Entonces, ¿de qué vamos a hablar hoy? Tenemos tres temas principales que queremos cubrir. Por supuesto, no profundizaremos en todos ellos porque tenemos 90 minutos, como puedo ver. Entonces hablaremos de qué es el bucle de eventos. Así que hablaremos brevemente de ello. Luego hablaremos de la cola de microtareas. Así que nuestro tema principal será ese. Así que vamos a deep dive en la cola de microtareas y luego haremos una demostración en vivo. Como todos sabemos, las demostraciones en vivo siempre salen bien durante los eventos. Así que les prometí que aprenderían algo. Sí. Así que alguien tenía que hacer algunos chistes malos y decidí que sería yo. Y por favor levanten la mano si saben qué es la cola de microtareas. Vale. Sí. Esperemos que sean un poco más tarde.
Vale. Así que comencemos con un par de premisas. Así que un paso atrás antes de comenzar nuestro verdadero viaje. Intentemos responder dos preguntas importantes hoy. Entonces, ¿es JavaScript de un solo hilo? Sí y no. Pero sí. Entonces saben que muchos de nosotros trabajamos con React, ¿verdad? Así que trabajamos en un navegador. Algunos de nosotros también trabajamos en Node. Y saben que pueden aprovechar múltiples hilos. Pero la especificación ECMAScript, digamos la lista de reglas alrededor del lenguaje, no permite la multitarea.
2. JavaScript Runtime y Engine
De hecho, JavaScript es de un solo hilo. Sin embargo, puede hacer cosas que solo un entorno multihilo podría permitirle hacer. JavaScript se ejecuta en una caja compuesta por dos partes: el motor (por ejemplo, Chrome V8) y el tiempo de ejecución (conjunto de API encima del motor). El navegador y Node.js comparten el mismo analizador y ejecutor, pero tienen diferentes API. Por ejemplo, Node.js tiene la API del sistema de archivos, mientras que el navegador proporciona API web como el DOM.
De hecho, JavaScript es de un solo hilo. Sin embargo, puede hacer cosas que solo un entorno multihilo podría permitirle hacer. Entonces, en primer lugar, respondamos a esta pregunta. Entonces, ¿qué es un JavaScript runtime? Digamos que JavaScript se ejecuta en una especie de caja, contenedor, llámalo como quieras, y está compuesto principalmente por dos partes. Tenemos el motor y el runtime. El motor es, por ejemplo, el motor Chrome V8. Entonces, es la parte que analiza y ejecuta nuestro código y se asegura de que todo esté en orden y lanza todo nuestro código. Y luego tenemos el runtime. El runtime es el conjunto de API encima de lo que el motor puede hacer. Entonces, tomemos el dúo más famoso en el ecosistema de JavaScript, que son, por supuesto, el navegador y el nodo, y hablaremos específicamente sobre el navegador Chrome. ¿Por qué eso? Porque tanto Chrome como V8, y el nodo comparten el motor V8. Entonces tenemos algo en común. El analizador y ejecutor del código es el mismo, pero, por supuesto, el nodo y el navegador son, bueno, bestias diferentes, ¿verdad? Entonces, en el nodo, tienes, por ejemplo, la API del sistema de archivos. Puedes llamar al sistema de archivos, pedir archivos, directorios, etc., lo cual es algo que no puedes hacer. No puedes hacerlo con la misma API exacta en el navegador porque el navegador proporciona su propio conjunto de API web. Pensemos en el DOM. No puedes interactuar con el DOM en el nodo porque no es parte del motor. El console.log, por ejemplo. ¿Sabías que hay dos implementaciones diferentes de console.log? Entonces, no es una parte específica del motor, sino que es proporcionada por los tiempos de ejecución. Vale. Entonces, ahora que hemos resuelto esas dos preguntas, dijimos que JavaScript es de un solo hilo aunque el nodo no lo es. Chrome tampoco lo es.
3. Entendiendo el Event Loop
Entonces, echemos un vistazo a lo que es efectivamente el bucle de eventos para poder responder mejor ambas preguntas. Tenemos tres partes principales: una pila de llamadas, una cola de microtareas y una cola de microtareas. La pila de llamadas es nuestra ejecución actual, mientras que las colas de microtareas se utilizan para liberar el hilo principal. El bucle de eventos proporciona orden en estos tres componentes. Las API como process.nexttick y set immediate interactúan con la cola de microtareas. JavaScript permite operaciones de ejecución hasta la finalización y aprovecha las colas para liberar el hilo.
Entonces, echemos un vistazo a lo que es efectivamente el bucle de eventos para poder responder mejor ambas preguntas. Como pueden ver, esta es una representación realmente impresionante de Lydia Ali del bucle de eventos. Y como pueden ver, tenemos tres partes principales. Tenemos una pila de llamadas, una cola de microtareas y una cola de microtareas. Por supuesto, hay muchas otras partes en movimiento, pero por el bien de la simplicidad, esto es suficiente. Y tenemos esta cosa llamada bucle de eventos allí que simplemente proporciona un orden en todas estas tres cosas.
Entonces, la pila de llamadas es nuestra ejecución actual. Vale. Entonces, lo que está haciendo nuestro hilo principal. Y luego tenemos la cola de microtareas y la cola de microtareas que se utilizan efectivamente para liberar nuestro hilo principal de, ya sabes, algún cálculo difícil o algunas funciones inmediatamente ejecutables. Así es como podemos tener una especie de ejecución multihilo incluso en un solo hilo runtime. Vale. En una ejecución de un solo hilo. Así que veamos este GIF en marcha. Como pueden ver, tan pronto como la pila de llamadas vacía las cosas de la cola de microtareas son llevadas a la pila de llamadas, y luego vamos a la cola de microtareas. Como pueden ver, hay algunas API simples que podemos usar para interactuar con la cola de microtareas que son process.nexttick, promesa de devolución de llamada, funciones asíncronas y la cola de microtareas. Y luego tenemos la cola de microtareas que establece el tiempo de espera, establece el intervalo y establece inmediato. Y sí, lo leíste correctamente. El process.nexttick es algo que se hace en el tick actual de la microtarea Y set immediate se hace efectivamente más tarde en el tiempo porque sí, somos bastante buenos en nombrar cosas en el ecosistema de JavaScript.
Entonces, hablemos de tres conceptos principales del bucle de eventos. Y ves que son como un semáforo allí. Lo primero que tenemos que mencionar son las operaciones de ejecución hasta la finalización. ¿Qué queremos decir con operaciones de ejecución hasta la finalización? Teniendo una sola pila de llamadas y teniendo un solo hilo de operaciones, JavaScript nos permite estar seguros de que nuestra ejecución va desde el principio hasta el final antes de que comience otra. Así que aclaremos. No hay concurrencia en JavaScript como tal. Podemos alcanzar algún tipo de concurrencia de alguna manera. Pero, por supuesto, este es uno de los conceptos principales para hacer funcionar el bucle de eventos. Así que solo una cosa a la vez puede hacerse en nuestra pila de llamadas. La segunda cosa que vale la pena mencionar es el aprovechamiento de las colas para liberar el hilo. Así que como dijimos antes, tenemos la cola de microtareas y la cola de microtareas, que son utilizadas para poner todas las operaciones no inmediatamente ejecutables.
4. Bloqueando el Hilo Principal y Operaciones I.O.
Y esto libera nuestro hilo y hace que nuestra pila de llamadas siga avanzando y nuestro hilo no se bloquee. El tiempo de espera no se concede. ¿Qué queremos decir con que el tiempo de espera no se concede? Tomemos, por ejemplo, un tiempo de espera establecido de 1,000 milisegundos. Tenemos todas estas otras cosas sucediendo antes de que la pila de llamadas ponga nuestras operaciones en su lugar y las ejecute. Esas tres cosas son las que hacen que el ecosistema de JavaScript no se bloquee incluso aprovechando un solo hilo. JavaScript es efectivamente un solo hilo. Node, Chrome, etc., nos permiten liberar nuestro hilo principal. Comencemos con el peor de los casos. Tenemos dos botones: uno es un bloqueo I.O. y el otro no es un bloqueo I.O. Como pueden ver, puedo hacer clic continuamente en el botón no bloqueante de I.O. y no pasa nada, pero tan pronto como hago clic en el segundo, todo se rompe.
Y esto libera nuestro hilo y hace que nuestra pila de llamadas siga avanzando y nuestro hilo no se bloquee. La tercera cosa es que el tiempo de espera no se concede. Esto es rojo porque es una bandera roja que suelo ver cuando desarrollo aplicaciones conmigo mismo. ¿Qué queremos decir con que el tiempo de espera no se concede? Tomemos, por ejemplo, un tiempo de espera establecido de 1,000 milisegundos. ¿De acuerdo? Entonces esperamos 1,000 milisegundos y luego ejecutamos nuestra operación. Si intentamos traer alguna marca de tiempo, veremos que 1,000 milisegundos es el mínimo de tiempo que tomará para que nuestra operación se ejecute. Porque como vimos antes, tenemos todas estas otras cosas sucediendo antes de que la pila de llamadas ponga nuestras operaciones en su lugar y las ejecute. Eso es lo que queremos decir con que el tiempo de espera no se concede. Y esas tres cosas son las que hacen que el JavaScript ecosistema sea no bloqueante incluso aprovechando un solo hilo. Por supuesto, cuando estamos trabajando en Node y en el navegador, podemos tener algunas operaciones de multihilo que pueden suceder. Pero aún así, nuestro hilo principal es solo uno y solo uno puede ejecutar nuestra pila de llamadas. ¿De acuerdo? Tenemos, ya sabes, hilos de trabajo. Tenemos web workers para la web, etc. Aún así, JavaScript es efectivamente un solo hilo. Por lo tanto, las operaciones principales van para la pila de llamadas principal. Entonces, como dijimos, Node, Chrome, etc., nos permiten liberar nuestro hilo principal. Así que descubrí que era interesante ver cómo bloquear efectivamente el hilo principal. ¿De acuerdo? Entonces, comencemos con el peor de los casos. Entonces aquí tenemos... ¿Es esto una llama o una alpaca? ¿Sabes cuál es la diferencia? ¿No? Bueno, uno no está en la foto. Entonces tenemos dos botones. Sí, es una llama, por cierto. Tenemos dos botones. Uno es un bloqueo I.O. y el otro no es un bloqueo I.O. Tengo esta imagen en movimiento. Ahora la haré empezar. Y como pueden ver, puedo hacer clic continuamente en el botón no bloqueante de I.O. y no pasa nada, pero tan pronto como hago clic en el segundo, todo se rompe.
5. Botón de Bloqueo I.O. y Bucle Inseguro
Veamos nuestra simple implementación de esos botones. Tenemos dos oyentes de eventos con un evento de clic, y llamamos al bucle inseguro y al bucle seguro. La función de bucle inseguro implementa un bucle infinito al pedir continuamente a nuestra pila de llamadas que traiga algo a la mesa. Esto puede llevar a llenar la pila de llamadas y hacer que el navegador se vuelva irresponsivo.
Bien. Entonces, ¿cómo puede suceder? ¿Y cómo podemos evitarlo? Veamos nuestra simple implementación de esos botones. Como pueden ver, tenemos dos oyentes de eventos con un evento de clic, y llamamos al bucle inseguro y al bucle seguro. Entonces, comencemos con nuestro botón de bloqueo I.O. Y como pueden ver, estamos llamando a la función de bucle inseguro. Entonces, ¿qué hace la función de bucle inseguro? Bueno, efectivamente, nada. Pero estamos implementando un bucle infinito. Entonces, ¿por qué es eso? Porque incluso si no hay operaciones entre los paréntesis, todavía estamos continuamente pidiendo a nuestra pila de llamadas que traiga algo a la mesa. Entonces, efectivamente vamos a llenar nuestra pila de llamadas hasta que Chrome, por ejemplo, sea lo suficientemente bueno para decirnos, hey, por favor detente. Pero en el pasado, tu computadora se quemaría. Entonces, este es el primer caso.
6. MacroTaskEq y Demostración en Vivo
El MacroTaskEq actúa como el último paso antes de que comience el siguiente tick. Todavía podemos pedir a nuestra pila de llamadas que se llene de cosas que hacer antes de volver a renderizar la página. Hoy, construiremos una demostración en vivo con cuatro pasos: estructura del proyecto, implementación de la clase JS, benchmarking e implementación de la cola de microtareas. Veamos algo de código.
Luego pasamos al botón no-IoBlockingButton, que hace casi exactamente lo mismo, pero llama a la API setTimeout. Y recuerden, estamos hablando del MacroTaskEq y esta es una diferencia importante que cubriremos más adelante. Así que el MacroTaskEq efectivamente actúa de una manera diferente a la MicroTask. Porque pensemos en el bucle de eventos como un círculo, ¿de acuerdo?, con un punto en él haciendo un 360. ¿De acuerdo? Y cada vez que llega al final, comienza otro tick. Entonces, en el navegador, ese tick puede ser la repintura de la página. Y cuando estamos aprovechando alguna API MacroTask como el setTimeout, estamos efectivamente pidiendo a nuestro punto que complete su tick. Y luego, cuando comienza el nuevo, podemos ver si hay algo en el MacroTaskEq para llevar a la pila de llamadas. Por eso podemos crear efectivamente un bucle no infinito incluso si esta ejecución continuará yendo así que nuestro hilo principal seguirá siendo libre porque estamos pidiendo a nuestro tick que termine cada vez antes de empezar a llamar de nuevo a nuestra función de bucle seguro.
Entonces podemos volver a nuestra pregunta inicial. Entonces, ¿qué es el MacroTaskEq? Bueno, esta es probablemente una de las partes más mal entendidas del bucle de eventos porque actúa en una parte específica de él. Y tomemos el ejemplo que hicimos antes. Así que tenemos este círculo con este punto girando 360 y cada vez que termina, comienza un nuevo tick. El MacroTaskEq actúa efectivamente como el último paso antes de que comience el siguiente tick. Así que estamos en el tick actual pero todavía podemos pedir a nuestra pila de llamadas que se llene de cosas que tenemos que hacer para la última parte antes, como un ejemplo en Chrome, de volver a renderizar la página, ¿de acuerdo? Entonces sí, hasta ahora todo bien pero, ¿qué estamos construyendo hoy? Les prometí una demostración en vivo y tenemos cuatro pasos para la victoria. El primero es una estructura de proyecto, así que una simple página HTML. Una implementación básica de señal aprovechando las clases JS. Sé que esta no es la mejor manera de hacerlo pero por el bien de la simplicidad, decidí optar por las clases porque todavía podemos ver muchas mejoras allí. La tercera parte sería un benchmark básico así que rastrearemos el tiempo necesario para, no sé, 100,000 actualizaciones, 1 millón de actualizaciones, lo que sea. Tengo un M1 max así que podemos llegar hasta 10 mil millones. Y la cuarta parte será, por supuesto, implementar la cola de microtareas. Así que veamos algo de código. Preparé una estructura simple así que el primer paso está hecho. Así que tenemos esta simple página de Chrome. ¿Todos pueden verla? ¿Está bien? ¿Está bien? Bien. Bueno. Así que tenemos nuestro párrafo, nuestro simple párrafo y haremos el peor anti-patrón de todos. Pondremos el script dentro del HTML. Ya saben, como en los buenos viejos tiempos cuando hacíamos eso.
7. Creando una Clase de Señal
Comencemos creando una nueva clase llamada señal. Tendrá un elemento HTML y un valor adjunto a él. El constructor tomará un elemento y un valor inicial. La función renderizará actualizará el elemento HTML con el nuevo valor. Finalmente, adjuntamos la señal al elemento HTML y creamos una nueva instancia de la clase de señal.
Entonces, comencemos creando una nueva clase que es una señal. Sí. Seamos claros. La clase es, como todos saben, azúcar sintáctica, pero hay muchos antecedentes aquí, no todos comenzamos con React, con JavaScript, etc. Así que esta es la mejor manera de hacerlo. Así que vamos a simplificar.
Entonces, nuestra implementación de señal solo tendrá un elemento HTML y un valor adjunto a él y la posibilidad de cambiar este valor actualizando el objeto. Sé que puedo usar algunas propiedades privadas como con el hash, etc., pero no quiero hacer eso. Quiero hacer esto por simplicidad, así que tenemos algunos, llamémoslos valores internos.
Tendremos nuestro constructor que tomará un elemento y un valor inicial y los guardaremos. Sí, gracias, Copilot. Vale. Entonces tenemos nuestro constructor simple. Por supuesto, no está haciendo nada. Y tenemos nuestra función de renderizado. Nuestra función de renderizado simplemente tomará nuestro elemento y le traerá un set peligroso en nuestro HTML a él. Vale. Así que recuerdas cuando era fácil simplemente traer algo de HTML interno a la página y ahora tienes que mantener una bandera, ya sabes, ponerla en verdadero. ¿Estás seguro de que estás haciendo eso? Vale. Entonces estamos llamando a la función dis.render y hemos terminado.
Ahora, por supuesto, tenemos que adjuntar nuestra señal a nuestro elemento HTML que llamamos párrafo. Así que llamémoslo párrafo de Londres. Vale. Y, por supuesto, creamos una nueva variable llamada myLondonSignal. Sí, si adivino correctamente. Nueva señal. Oh, sí, gracias de nuevo, copiloto. Entonces sí, estamos llamando a la nueva señal. Entonces estamos llamando a nuestro constructor con document get element por el párrafo de Londres y nuestro valor inicial. Vale.
8. Implementación de Señal y Benchmarking
Así que veamos si funciona. Por supuesto, no está funcionando porque no estamos llamando a la función de renderizado. Así que ahora funciona. Londres es increíble. Tenemos nuestra implementación de señal con funciones de obtener valor y establecer valor. Si intento cambiar el valor de esta señal, funciona. Ahora hagamos un benchmarking. Guardamos nuestro tiempo de inicio y actualizamos el valor 100,000 veces, tomando 70 milisegundos.
Así que veamos si funciona. Por supuesto, no está funcionando porque no estamos llamando a la función de renderizado. ¿Qué tan estúpido soy? Entonces, sí, intentemos... Vale. Así que ahora funciona. Londres es increíble. Woo hoo.
Vale, así que tenemos nuestra implementación de señal, y por supuesto tenemos que traer algo de magia salada aquí. Así que tenemos un obtener valor que puedes aprovechar más tarde. Eso pasará nuestro valor interno. Y por supuesto un establecer valor con un nuevo valor. efectivamente actualizará nuestro valor interno y llamará a nuestra función de renderizado. ¿Vale? Hasta ahora todo bien. Si intento... ¿Cómo lo llamé? MyLondonSignal. Si intento cambiar el valor de esta señal, funciona. Así que es una implementación de señal bastante simple, y ahora podemos hacer un benchmarking alrededor de ella.
Por favor, copiloto, ayúdame, porque nunca recuerdo la API de date.now. Vale. Así que guardamos nuestro tiempo de inicio. Por simplicidad, voy a optar por la fecha. No voy a optar por la gestión de microtiempos, etc. Así que vamos a... Voy a optar por, sí, 100,000 actualizaciones. ¿Sabías que puedes escribir enteros así en la nueva versión de la especificación ECMAScript? Sí. Es realmente impresionante. Vale. Intentemos actualizar este valor 100,000 veces, y veamos cuánto tiempo necesitó para actualizar esos valores. Vale. Como puedes ver, está tomando 70 milisegundos.
9. Aprovechando la API de la Cola de Micro-Tareas
Por supuesto, estamos en una máquina bastante potente. Así que subamos un poco el juego. Vamos a por 1 millón. Sí, nos estamos acercando al segundo. Vale, 600 milisegundos. Podemos actualizarlo un par de veces. ¿Sí? Vale, así que ahora estamos listos para aprovechar nuestra API de la cola de micro-tareas. Así que en primer lugar, vamos a hablar de una cola. Estamos poniendo efectivamente esta operación de renderizado en una cola. En nuestro constructor, proporcionaremos un valor inicial a false. Si esto ya está en cola, retorna. Pero si no, window.queue-micro-task es tan fácil como eso. Así que ahí lo tenemos. Esto es algo de lo que hay que estar consciente, porque la función de renderizado no es el mejor lugar para este tipo de cosas, pero tenemos un lugar mejor, que es el set value.
Por supuesto, estamos en una máquina bastante potente. Así que subamos un poco el juego. Vamos a por 1 millón. Sí, nos estamos acercando al segundo. Vale, 600 milisegundos. Podemos actualizarlo un par de veces. ¿Sí? Vale, así que ahora estamos listos para aprovechar nuestra API de la cola de micro-tareas, y ¿qué tan difícil puede ser eso? Bueno, es bastante simple, de hecho. Así que en primer lugar, vamos a hablar de una cola. Vale, así que estamos poniendo efectivamente esta operación de renderizado en una cola. Así que digamos que está en cola. Vale, solo un simple valor interno que podemos aprovechar más tarde. En nuestro constructor, por supuesto, proporcionaremos un valor inicial a false. Nuestra función de renderizado no se ha tocado hasta ahora y vamos a hacer la magia justo aquí. Así que si esto ya está en cola, retorna. Pero si no, window.queue-micro-task es tan fácil como eso, y podemos pasar una función aquí para decir, hey, vale, cuando estés en la cola de micro-tareas, solo di que esto está en cola igual a true. Así que cada vez que llamamos de nuevo a la función de renderizado, no se llama. Y vale. Así que ahí lo tenemos. Así que si no hice nada mal aquí, sí, vale, hice algo mal. Sí, no es un problema. Sí, porque lo moví al lugar equivocado. Así que esto es algo de lo que hay que estar consciente, porque la función de renderizado no es el mejor lugar para este tipo de cosas, pero tenemos un lugar mejor, que es el set value. Y por supuesto, vamos a volver a nuestra implementación inicial. Vale. Este punto de fallo. Y aquí vamos a decir, si esto digamos que si no, esto está en cola. Window dot queue microtask. Escribámoslo de nuevo, solo para estar seguros de que no hacemos nada mal aquí. Esto está en cola igual a true. Este punto de valor.
10. Uso de la Cola de Microtareas y Beneficios de Rendimiento
Y este punto render. Así que vamos a eliminar esos de aquí. Y está bien. Sí. Vamos a refrescarlo. Bien. Como pueden ver, tuvimos algunos beneficios de rendimiento, no tanto como imaginábamos, pero por supuesto es solo por la simplicidad de esta demostración. Y por supuesto puedes hacer esto en muchas partes de tus aplicaciones. Y si lo haces correctamente, no como lo estoy haciendo ahora, ya que me queda un minuto para completar esta demostración, puedes alcanzar efectivamente una mejora de rendimiento de 100x en tu implementación de señal.
Y este punto render. Así que vamos a eliminar esos de aquí. Y está bien. Sí. Vamos a refrescarlo. Bien. Como pueden ver, tuvimos algunos beneficios de performance, no tanto como imaginábamos, pero por supuesto es solo por la simplicidad de esta demostración. Y por supuesto puedes ir... puedes hacer esto en muchas partes de tus aplicaciones. Y si lo haces correctamente, no como lo estoy haciendo ahora, ya que me queda un minuto para completar esta demostración, puedes alcanzar efectivamente una mejora de rendimiento de 100x en tu implementación de señal.
Así que si lo que expliqué hasta ahora es bueno, fue suficientemente bueno, ese fue un resultado realmente inesperado. Si no, lo siento. Así que puedes echar un vistazo a la charla de Jake Archibald en el loop, que es realmente impresionante y realmente profundiza aún más en la cola de microtareas y cómo sus mecanismos internos, ya sabes, están interactuando con nuestro bucle de eventos. Y ahora que, por supuesto, vimos como una mejora de 3x, 4x en nuestro rendimiento, hey, es realmente impresionante. Es como un 25% menos de los rendimientos que teníamos antes. Te preguntaré de nuevo, así que por favor levanta la mano si ahora sabes lo que es la cola de microtareas. Sí, al menos uno más que antes.
Bien. Entonces, ¿por qué no siempre usamos la cola de microtareas, te preguntarás. Así que, vimos que esta implementación es fácil. Es una API simple. En algunas otras demostraciones de algunas otras implementaciones, literalmente pasé de 600 milisegundos a 6, 7 milisegundos, y tendré un quadcode más tarde. Entonces, ¿por qué no siempre usamos la cola de microtareas? Bueno, como nos gusta decir en Italia, no todo lo que brilla es oro. Como vimos antes, la cola de microtareas actúa antes de que comience el siguiente tick. Entonces, ¿qué significa esto? Que si vamos a nuestra implementación anterior del bucle seguro y simplemente lo cambiamos a un promise.resolve.then, estamos creando efectivamente un nuevo bucle infinito. Entonces, ¿por qué es eso? Porque el promise.resolve pone la función de bucle seguro en la cola de microtareas. Nuestro punto va hasta el final de nuestro bucle de eventos y dice, bien, cola de microtareas, ¿tienes algo para mí? Sí, seguro. Toma este bucle seguro. Por eso es un promise.resolve.then. Vamos a llevarlo a la cola de microtareas. Vamos a hacer esto de nuevo.
Cola de Microtareas y Casos de Uso
¿Tenemos algo? Sí. Puedes aprovechar la cola de microtareas para los últimos pasos antes de refrescar o repintar tu página. Los casos de uso incluyen operaciones de agrupación y ordenación de arrays. Consulta la implementación de señal basada en prototipos en mi GitHub para una mejora de rendimiento de cien veces. Ahora, pasemos a las preguntas.
¿Tenemos algo? Sí. Y estamos creando efectivamente un nuevo bucle infinito. Así que, por favor, ten en cuenta que esto puede, puedes aprovechar esto para hacer algunos últimos pasos antes de refrescar tu página, repintar tu página, pero aún así, puedes encontrar algunos bucles infinitos allí. Así que, como me gusta decir, el bucle de eventos es el único bucle infinito que quieres tener en tu aplicación. Así que, por favor, recuerda lo que dijimos hoy sobre la cola de microtareas.
Y, sí, sé que han pasado 20 minutos, pero voy a hacer la pregunta, sí, así que, supongo que está bien. Puedes preguntar algunos casos de uso para ello porque, por supuesto, no necesitas actualizar un millón de veces tu párrafo en tu página. Y bien, todo lo que puede ser agrupado como una operación puede ser literalmente construido en una cola de microtareas para ser ejecutado sólo una vez. Así que, tomemos, por ejemplo, un array de nombres que tienes que ordenar cada vez que uno nuevo llega y tienes una importación CSV en tu página. No sé. Así que, traes el CSV, tomas todos los nombres, y cada vez que llega un nuevo nombre, reorganizas el array. Así que, con esta implementación, puedes simplemente decir, ¿ya pedí que mi array fuera reordenado? Vale. Así que, haz esto en la cola de microtareas. Y tal vez en la microtarea, puedo poner un use state en algún lugar. Vale. Así que, es como una conferencia de micrófonos. Así que, pongamos las cosas en un estado. Así que, pones las cosas en el estado. Y luego sólo una vez, se realiza la operación. Así que, si quieres ver una mejor implementación y una más impresionante porque es literalmente un cambio de cien veces, puedes mirar un simple repositorio que creé que es una señal super simple, por supuesto. Y es similar a éste, pero está usando el enfoque basado en prototipos. Y está en mi GitHub. Puedes echarle un vistazo. Es lo suficientemente simple. Es sólo una página de JavaScript. Son como 40 líneas de código si no me equivoco.
Y aquí estamos para las preguntas. Muchas gracias, Michael. Gracias. Sí.
Uso de la Cola de Microtareas y Sus Posibles Problemas
La cola de microtareas debería utilizarse fuera de la función de renderizado, pero no siempre. Es importante encontrar un compromiso entre las tareas de pre-renderizado y post-renderizado. Muchas personas subutilizan la cola de microtareas porque no entienden su papel en el bucle de eventos. Las promesas dependen en gran medida de la cola de microtareas, pero su uso incorrecto puede llevar a bucles infinitos. Puede parecer contraintuitivo, pero las promesas pueden ejecutarse inmediatamente y no necesariamente más tarde.
Eso fue excelente. Muchas gracias por guiarnos a través de eso. Creo que lo que más me hizo pensar fue, por supuesto, los casos de uso. Creo que deberíamos ir directamente a algunas de las preguntas que surgieron en torno al uso de la cola de Microtareas. Destaquemos rápidamente. Entonces, siempre deberíamos usar la cola de microtareas fuera de la función de renderizado.
¿Deberíamos siempre usar la cola de microtareas fuera de la función de renderizado? No, no siempre. Eso es lo que estaba tratando de decir en mi última diapositiva. Así que por favor, ten en cuenta los beneficios que puede proporcionar. Pero por supuesto, recuerda que también tenemos la cola de microtareas que podemos aprovechar cuando no necesitamos hacer cosas antes del renderizado. Así que solo para encontrar un buen compromiso entre lo que necesito antes del renderizado y lo que puede esperar después de la fase de renderizado.
Genial. Y sobre ese punto, ¿hay... ¿Por qué crees que la cola de microtareas es tan a menudo potencialmente subutilizada o...? Creo que es... No diré que no se utiliza porque las promesas dependen en gran medida de la cola de microtareas. Pero muchas personas usan la cola de microtareas sin saber efectivamente en qué parte del bucle de eventos actúa. Así que ese es el problema del que hablaba antes. Entonces, ¿por qué no las usamos siempre? Porque efectivamente son otra forma de crear un bucle infinito. Algo que no haces con la cola de microtareas. Así que deberías estar al tanto. La implementación de las promesas se basa totalmente en la cola de microtareas. Así que si no sabes cómo depender de la cola de microtareas, es fácil caer en un bucle infinito. No puedes entenderlo porque estás diciendo, sí, está bien, es una promesa. Es más tarde en el tiempo. Sí, pero no en el bucle de eventos si la resuelves inmediatamente.
Tiene sentido y se siente bastante contraintuitivo. Sí. Sí, porque no es efectivamente más tarde. Puede ser, por supuesto, si la promesa es, no sé, una llamada AJAX, etc., pero aún puede ser inmediata. Hay algunas, hay muchas bibliotecas que dependen de las promesas pero se ejecutan inmediatamente.
Manejo de Promesas y Micro-tareas en React
Puedes encontrarte en un bucle infinito si no sabes dónde actúa esto. Hay algunas pequeñas diferencias en el manejo de promesas y micro-tareas entre los navegadores. La agrupación de operaciones ha sido el caso de uso más útil para mejorar el rendimiento en React.
Sabes, solo por simplicidad, puedes tener, no sé, una llamada HTTP y un Mock para tu implementación local que no proporciona la llamada HTTP pero se resuelve en una promesa. Vale. Entonces, pensemos en los servidores Mock. Normalmente hacen eso con las APIs, las APIs Mock. Y entonces, sí, puedes encontrarte en un bucle infinito si no sabes dónde actúa esto. Y dices esto bastante rápido, como, fácilmente lleva a nuestra siguiente pregunta que está resaltada en la pantalla detrás de nosotros.
¿Hay alguna diferencia en el manejo de promesas y micro-tareas entre los navegadores? Sí, no soy un experto en motores de navegadores, vale. Pero sé que hay un error en Safari. Oh. Sí, donde en algunos casos las promesas se retrasan en la cola de macro tareas y también hay un mecanismo seguro en Chrome en el que si puede detectar con su propio analizador, si puede detectar la posibilidad de un bucle infinito, mueve las promesas a la cola de macro tareas. Vale. Entonces, sí, hay algunas pequeñas diferencias, pero por supuesto son solo algunos casos de uso específicos que no deberías ver en tu vida. Y realísticamente, los descubrirás a medida que desarrolles en un navegador. Sí. Sí.
Realmente me gusta la siguiente también, de hecho. Voy a traer esta. Sería bueno entender la variedad de contextos dentro del mundo de React en los que podemos utilizar esto para mejorar el performance. Así que no es exactamente una pregunta, pero creo que es un poco de props. Sí. Perdona el horrible juego de palabras. Entonces, ¿qué tipo de casos de uso has encontrado más útiles? Bueno, como dije antes, son las operaciones de agrupación. Sabes, he estado trabajando con React durante los últimos años al menos y me encontré luchando para encontrar la mejor manera de mejorar el rendimiento. Y cuando descubrí el QMicroTask, encontré tantas formas en las que no podía depender del mecanismo de callback de use state. Sabes, cuando tienes un estado y luego tienes un efecto en ese estado que llama a otro set state, etc. Y usando el QMicroTask, puedes aprovechar algo que está incorporado en el navegador, ¿vale? Es el bucle de eventos. Es cómo funciona JavaScript. Así que no tienes que depender de alguna magia del lado de React, sabes, con el suspense, fiber, etc. No tienes que ir a ese mundo mágico donde solo Dan Abramov puede entender las cosas. Y puedes quedarte en el suelo y decir, vale, sé cómo funciona.
Bucle de Eventos y Manejo de Errores en JavaScript
No me importa cómo lo maneje React. Sé que puedo hacer esta operación de agrupación, esta operación de clasificación, sabes, este conjunto de cosas que necesito antes de renderizar sin pedir a React que vuelva a renderizar mi componente cuatro, cinco, seis veces. El bucle de eventos maneja el manejo de errores y la propagación en las aplicaciones de JavaScript de manera similar a cómo funcionan los mecanismos de try-catch. El uso de microtareas puede ser abusado y causar problemas de rendimiento, pero si se usan correctamente, pueden ser beneficiosos. El principal caso de uso de las microtareas en React es para prevenir la re-renderización innecesaria de la página.
No me importa cómo lo maneje React. Sé que puedo hacer esta operación de agrupación, esta operación de clasificación, sabes, este conjunto de cosas que necesito antes de renderizar sin pedir a React que vuelva a renderizar mi componente cuatro, cinco, seis veces. Iba a decir, es bastante común ‑‑ Me pregunto si depende o no de dónde se encuentre tu experiencia. Depende si terminas trabajando con React o contra React o trabajando con JavaScript o contra JavaScript.
¿Puedes reformularlo de nuevo? Oh, estás hablando de usar el método incorporado en el navegador. Sí. Simplemente confiando en el navegador, porque no cambia. Por cierto, si alguien quiere agregar alguna pregunta, si tienes la aplicación Slido, porque sé que mucha gente ha descargado Slido para varios ayuntamientos o eventos que has estado usando, si usas el código, creo que es 2010, podrás saltar a la sesión, elegir la pista, y agregar tu pregunta a la lista aquí. Así que si se te ocurre alguna que pienses, en realidad creo que quiero preguntar eso para mi trabajo en este momento, esa es una buena manera de entrar en la cola.
Entonces, tenemos aquí arriba, tenemos, ¿cómo maneja el bucle de eventos el manejo de errores y la propagación en las aplicaciones de JavaScript? Bueno, efectivamente, no hay diferencia entre cómo manejas realmente un mecanismo de try-catch para el manejo de errores. Porque como dijimos antes, estamos en el terreno, en el bucle de eventos, la forma normal de trabajar. Así que si tienes una promesa, todavía puedes usar el catch para el manejo de errores, no hay nada particular en cómo usar la cola de microtareas. Todavía puedes usar un try-catch, puedes usar un sink away. También puedes, aunque no se recomienda, puedes tener una función asíncrona siendo llamada en la función de devolución de llamada de la cola de microtareas. Y como dije antes, no lo recomendaría porque algunos navegadores como Chrome pueden detectarlo y moverlo a la cola de microtareas para evitar que el hilo principal se bloquee por una llamada demasiado dura en el async.
Creo que casi sin querer has respondido a nuestra siguiente pregunta que hemos resaltado aquí. La respuesta es que en general no deberíamos usar microtareas ya que puede ser abusado y causar problemas de rendimiento en las computadoras de los clientes. Sí, eso es cierto, pero también es cierto que no deberías tener cinco use effects para seis set states que vuelven a renderizar la página cinco veces. Así que, tienes que estar al menos consciente de cómo estás aprovechando el bucle de eventos y saber que existe la cola de microtareas que por supuesto puede ser abusada. Pero si sabes lo que estás haciendo, creo que estás a salvo. Así que creo que ese es el punto de mi charla, si sabes lo que estás haciendo, creo que estás a salvo. Bien, creo que podríamos tener tiempo para una o dos preguntas más. Creo que las siguientes son algo así, las dos siguientes aquí, tenemos ¿puedes aplicar esto a el contexto de React y funcionará en la función de React, así que creo que están preguntando un poco el mismo contexto. Como dije, el mejor caso de uso que encuentro es evitar depender de todos estos estados de afecto callback. Vale. Así que este es el caso de uso más común que veo para la Microtarea. Donde puedes efectivamente prevenir que la página se vuelva a renderizar de una manera inútil porque va a volver a renderizar tres, cuatro, cinco veces. Sí. Así que ese es el principal caso de uso que veo a primera vista para React. Me pregunto si deberíamos...
Colas del Navegador y VS Code
El navegador tiene muchas colas, cada una con su propia implementación de cómo interactúa con el bucle de eventos. En un entorno de bucle de eventos simple, generalmente hay solo dos colas. La cola de interacción de la interfaz de usuario de Chrome es un ejemplo donde prioriza volver a renderizar la página antes de procesar la cola de microtareas. Nos reuniremos después de un descanso a las 11.30. Si alguien tiene preguntas urgentes o si hay alguna pregunta de interés restante, no dude en levantar la mano. La entidad IP que se utiliza es VS Code, Visual Studio Code, desarrollado por Microsoft.
¿Tienes alguna preferencia sobre la próxima pregunta que te gustaría responder aquí? Porque siento que te he estado guiando a través de un par de fundamentos.
Sí. La primera es agradable y vale la pena mencionar. Así que es... También vale la pena mencionar que el navegador tiene muchas colas. Sí. Así es. Sí. Por supuesto, tenemos algún concepto de priorización. Eso es lo que decía antes cuando mostré el gif de Lydia Halle. Porque hay tantas partes móviles y el navegador tiene su propia implementación de cómo interactúa con el bucle de eventos. Eso es, por supuesto, diferente de Node, de BUN, de Dyno, de Safari, Runtime, etcétera.
Sí, vale la pena mencionar que generalmente solo hay esas dos colas en un simple entorno de bucle de eventos. Pero, por supuesto, puede haber muchas. Como ejemplo, está la interacción de la interfaz de usuario de Chrome donde, por supuesto, detecta esos infinitos de los que hablábamos antes y trae la cola de microtareas en una baja prioridad para que vuelva a renderizar la página antes de entrar en la cola de microtareas. Sí. Creo que estamos llegando al momento en que será un descanso para todos y nos reuniremos a las 11.30, pero me pregunto si alguien tenía alguna pregunta urgente en la sala o si alguna de las preguntas de las últimas restantes realmente te están llamando la atención. Si tenemos... No dudes en levantar la mano si tienes algo que... Oh, primera fila. Primera fila, ¿cuál es el nombre de la entidad IP que estás usando? Me gustan tus tokens. Es VS Code. Es Visual Studio Code. VS Code. VS Code. Sí. Sí. De Microsoft... Y estabas haciendo un gran uso de Copilot allí. Sí.
Cola de Microtareas y Renderizado
Me encanta eso. El concepto principal de la microtarea es que actúa ligeramente antes del renderizado. Cuando tienes 1000 actualizaciones que hacer, estás volviendo a renderizar la página muchas veces. La cola de microtareas actúa solo una vez, lo que reduce significativamente la diferencia de tiempo. Gracias, Michael, por la charla.
Me encanta eso. Sí. Porque sabes, tengo algo así como 20 intentos de esta charla y sí. Por eso el Copilot sabe cómo ayudarme en esta situación.
Así que hay una pregunta que me gustaría responder y es la primera. Sobre el hecho de que el renderizado ocurrió después de que terminé y cronometré mi bucle. Sí. Ese es el concepto principal de la microtarea. Así que actúa ligeramente antes del renderizado. Por eso.
Así que cuando tienes 1000 actualizaciones que hacer, por ejemplo, para nuestro andamiaje y la implementación de C, estás volviendo a renderizar 100,000 veces, un millón de veces la página. Y por supuesto, en M1 Max, no ves tanta diferencia. Pero puedo garantizarte que, cuando hice este ensayo, este ejemplo en mi propio portátil, que es un M1, así que no es una mala máquina, la diferencia de tiempo para esa misma implementación, así que con las clases, que por supuesto no es la mejor que puedes ver, literalmente pasé de dos segundos a 30 milisegundos. Así que sí, es correcto que la cola de microtareas actúa solo una vez. Por eso pusimos el booleano is in queue allí, solo para decir, vale, hazlo solo una vez. Esa es la principal diferencia. Brillante. Bueno, muchas gracias, Michael, realmente lo aprecio, y gracias por la charla. Creo que volveremos al resto de la cola del evento de hoy.
Comments