La API AsyncLocalStorage en Node.js es una herramienta poderosa que ha sido adoptada en múltiples entornos de ejecución. Incluso hay un esfuerzo en marcha para estandarizar una variación de la API dentro de JavaScript mismo. Pero, ¿qué es? Y, lo que es más importante, ¿cómo funciona?
This talk has been presented at Node Congress 2024, check out the latest edition of this JavaScript Conference.
El almacenamiento local asíncrono es una API que ha estado presente en Node durante bastante tiempo y está ganando popularidad en otros frameworks y entornos de ejecución. Permite un registro más sencillo al eliminar la necesidad de pasar valores a través de múltiples funciones. El marco de almacenamiento en el almacenamiento local asíncrono actúa como un mapa, almacenando pares clave-valor. Las tareas y las continuaciones de promesas se utilizan para realizar los siguientes pasos en una cadena de promesas. Async Context es una propuesta de TC39 que agrega almacenamiento local asíncrono al lenguaje JavaScript.
Hola, soy James Snell, un ingeniero principal en Cloudflare y miembro del Comité de Dirección Técnica de Node. Hablaré sobre async local storage y cómo funciona, brindando información sobre esta API que ha estado presente en Node durante bastante tiempo y ahora está ganando popularidad en frameworks y entornos de ejecución como Deno y Workers. Vamos a adentrarnos en los detalles.
Muy bien, hola Node Congress. Soy James Snell. Voy a hablar un poco sobre async local storage y cómo funciona. Es una API que ha estado presente en Node durante bastante tiempo, varios años. Y ahora está siendo adoptada por varios frameworks, como NextJS y algunos otros, y se está implementando en una variedad de entornos de ejecución como Deno y Node y Workers y Bun. Así que quiero hablar sobre cómo funciona. Adentrarnos un poco en los detalles para que puedas entender qué está sucediendo. En cuanto a mi experiencia, soy James Snell. Soy un ingeniero principal en Cloudflare. Trabajo en la plataforma Workers. También he sido miembro del Comité de Dirección Técnica de Node durante, wow, casi una década. He estado contribuyendo a Node desde 2015. Y soy el copresidente y uno de los fundadores de WinterCG, que es el Grupo de Comunidad de Ejecuciones Web Interoperables donde nos enfocamos en reunir todas estas diferentes ejecuciones, como Bun y Deno y Workers, para hablar sobre APIs estandarizadas en general. Eso soy yo. Si has oído hablar de mí antes, probablemente sea por algunas de las contribuciones que he hecho a Node a lo largo de los años, como el WG URL, la implementación de web crypto, la implementación de web streams, ese tipo de cosas. Pero sí, vamos a ello.
2. Comprendiendo async local storage
Short description:
Vamos a adentrarnos en los detalles de async local storage y entender cómo funciona en profundidad.
Muy bien, si observamos la documentación de async local storage en el sitio web de Node, esta es la introducción muy útil que proporciona, hablando sobre cómo estas clases se utilizan para asociar estado y propagarlo a través de devoluciones de llamada y cadenas de promesas. No hace mucho para ayudarnos a entender qué está haciendo esta API en profundidad. Y si no lo has notado, las docs de Node en general no explican muy bien las cosas. Así que vamos a adentrarnos un poco más en los detalles, explicar cómo funciona, cómo
3. Usando async local storage para el registro
Short description:
Imaginemos que tenemos una función que necesita registrar un mensaje con un ID de solicitud. Antes de async local storage, teníamos que pasar el ID a través de todas las funciones que lo necesitan. Sin embargo, async local storage nos permite crear un contenedor y especificar el valor con el que queremos ejecutarlo. Las otras funciones no necesitan conocer el ID de solicitud, simplemente pueden acceder a él desde el contenedor cuando sea necesario. Esto facilita mucho las cosas, especialmente al lidiar con dependencias externas. Podemos establecer el contexto y recuperarlo más tarde sin modificar todo lo demás.
todo se junta. Muy bien, veamos un ejemplo. Este es un ejemplo canónico que me gusta usar para async local storage. Imaginemos que tenemos una función. Sabes, tenemos esta función de exportación predeterminada aquí. Esta es la forma de los trabajadores de exportar un controlador de función. Tenemos este fetch asíncrono. Queremos hacer algo y eventualmente queremos que se registre un registro aquí. Entonces, sabes, tenemos esta función de registro que registra un mensaje. Piensa en ello como un simple registro de console, eso tipo de cosas. Y lo que queremos hacer es definir un ID de solicitud que se registre con el mensaje cuando se envíe. Ahora, la forma tradicional de hacerlo antes de async local storage, la forma en que tendríamos que hacer esto es, sabes, tendríamos que generar un ID de solicitud. Entonces, aquí estamos usando la constante ID igual a crypto random UUID. Y en realidad tenemos que pasar ese ID a través de todas las diferentes funciones que lo necesitan, o que terminan en una declaración de registro. Entonces, aquí tenemos que modificar do something y do something else para tomar ese ID de solicitud y enviarlo a log, aunque do something y do something else no hagan nada más con el ID de solicitud en sí. Entonces, ya sabes, esto se complica un poco si estas funciones a las que estás pasando esto están en alguna otra biblioteca o alguna dependencia en tu code. O si están, ya sabes, o si están realizando tareas asíncronas complejas, ese tipo de cosas. Se vuelve un poco incómodo tener que modificar todas estas funciones solo para pasar este ID a lo largo para poder ingresarlo en la declaración de registro. Entonces, ¿y si hubiera una forma en la que pudiéramos simplemente decir, aquí está el ID, cada vez que se ejecute log, usa este ID, pero no tener que ser obligados a pasar eso a través de todas las diferentes capas de funciones y cosas que queremos llamar. Y eso es lo que nos ofrece async local storage, nos permite hacer. Con async local storage, podemos crear este, este contenedor, este, ya sabes, request ID igual a new async local storage, este es el contenedor. Y podemos especificar en nuestro controlador de fetch que, oye, queremos ejecutar con este valor, en este caso, es nuestro ID de solicitud, es un UUID aleatorio. Queremos ejecutar este code con ese valor establecido. ¿Verdad? Y esos, ya sabes, qué cambios aquí en do something y do something else, no tienen que saber nada sobre el ID de solicitud en absoluto. No tienen que pasarlo, excepto como un argumento. Cuando se llama a log, en realidad no tiene que decirle, ok, ¿bajo qué ID de solicitud estás ejecutando actualmente? Cuando se llama a log, cuando se invoca log, irá a ese contenedor, ese async local storage container, y simplemente dirá, oye, obtén el valor que está actualmente establecido para esto cuando se está ejecutando en este momento. Entonces nos dará una forma realmente sencilla de simplemente, oye, almacena este valor más adelante en alguna otra capa, queremos obtener el valor. Y observa que estamos haciendo esto con awaits. Entonces esto sucede de forma asíncrona a través de múltiples pasos de la, de una rama de promesa. Entonces, ya sabes, facilita mucho las cosas, especialmente si estas funciones do something y do something else provienen de alguna dependencia sobre la que no tienes control y que en realidad no puedes modificar de esta manera. Puedes establecer ese contexto y recuperarlo más tarde sin realizar ninguna modificación en todo lo demás.
4. Explorando el Marco de Almacenamiento
Short description:
Vamos a explorar lo que sucede detrás de escena en async local storage. En Node, el marco de almacenamiento es un concepto abstracto que actúa como un mapa, almacenando pares clave-valor. Cada variable de async local storage es una clave y tiene un valor en cualquier momento dado. El valor es inicialmente indefinido pero se establece cuando llamas a run. La recuperación del valor actual se hace con ALS y get store. Este marco, o mapa, no se limita a un solo marco en la aplicación.
Entonces, ese es el beneficio de async local storage, pero ¿qué sucede detrás de escena para que esto funcione? Vamos a ver eso. Muy bien. Tenemos este concepto de un marco de almacenamiento. Ahora, esto es en Node. El marco de almacenamiento es realmente más un concepto abstracto y en algunos otros entornos de ejecución como los workers, en realidad tenemos una clase interna llamada, ya sabes, async context frame y básicamente lo que es, es un mapa, ¿verdad? Todo lo que hace es almacenar pares clave-valor. Es como un mapa bastante simple.
La forma en que se relaciona con async local storage es que cada variable de async local storage, cada instancia de eso, es una clave. Y en cualquier momento dado, esa clave, esa instancia de async local storage tiene un valor. Ahora, inicialmente ese valor siempre es indefinido. Pero cada vez que llamas a run, se establece ese valor. Y luego, cada vez que llamas a ALS y get store, recupera el valor actual en ese mapa. Así que piénsalo, solo este marco, que es esencialmente solo un mapa. También podríamos llamar a esto, ya sabes, el mapa de async local storage.
5. Trabajando con Marcos de Almacenamiento
Short description:
Los marcos de almacenamiento son inmutables y utilizan una estrategia de copia al escribir. Cuando se establece un nuevo valor para una variable de almacenamiento local asíncrono, el marco existente se copia en una nueva instancia. Esto permite recuperar los valores establecidos en marcos anteriores, incluso si ha habido copias desde entonces. El marco inicial está vacío y los marcos subsiguientes se crean según sea necesario. Llamar a ALS run crea un nuevo marco y establece el valor especificado. Las llamadas posteriores a ALS run crean nuevos marcos y agregan valores, siendo el marco más reciente el actual.
¿verdad? Importante, no hay solo un marco en la aplicación. Los marcos de almacenamiento son inmutables. Utilizamos una estrategia de copia al escribir. Entonces, básicamente, cada vez que se establece un nuevo valor para una nueva variable de almacenamiento local asíncrono, en realidad copiamos el marco existente en una nueva instancia y luego establecemos ese valor allí. Es muy importante para el rendimiento. Entraremos en detalles sobre cómo funciona esto en unos minutos.
Pero, ya sabes, es importante entender que una vez que se crea un marco de almacenamiento, esa instancia debe permanecer sin cambios. Es inmutable. Y lo que eso nos permite hacer es que en cualquier momento que queramos hacer referencia a ese marco en cualquier parte de esa cadena de promesas o flujo de contexto asíncrono, siempre podemos recuperar el valor que se estableció en ese marco, en el marco relevante que se estableció, incluso si hemos copiado cualquier cantidad de marcos desde entonces. Entraremos en más detalles sobre esto, pero, ya sabes, el punto de esto es mostrar los primitivos muy simples que se utilizan aquí, un mapa, copiar y escribir, ¿verdad? Solo estamos estableciendo estos valores. Son solo claves y valores. Eso es todo. Entonces, inicialmente, cuando inicias tu aplicación, el marco inicial, siempre hay un marco. Siempre habrá un marco, un marco actual. Inicialmente está vacío. No tiene valores en él en absoluto. La primera vez que llamamos a ALS run, y ALS es un almacenamiento local asíncrono, la primera vez que llamamos a run y especificamos un valor, lo que sucederá es que el marco inicial vacío se copiará en una nueva instancia. Y luego, en esa nueva instancia, estableceremos ese ALS como la clave igual a este valor que pasamos, este uno, dos, tres en este caso. Y observa que cuando estamos ejecutando dentro de ALS run, ese nuevo marco que copiamos se convierte en el marco actual. Ese marco inicial todavía existe. Todavía está ahí. Pero ese nuevo marco se convierte en el marco actual. Y cuando llamamos a ALS dos, otra instancia de almacenamiento local asíncrono, llamamos a ALS two run, terminamos copiando ese segundo marco en un tercero, y luego estableciendo ese valor de ALS two además del ALS one, two, tres que ya estaba allí.
6. Funcionamiento interno de ALS Run
Short description:
ALS run establece un nuevo marco como actual y ejecuta un callback. El marco original se restaura después. Cada marco es una copia del marco anterior y establece un nuevo valor. Este proceso asegura que se utilice el marco correcto en cada actividad asíncrona.
Entonces, en este caso, ahora tenemos tres instancias de marcos de almacenamiento. ALS run se ha llamado dos veces, pero luego, en el interior, este segundo ALS two run, es este tercer marco el que realmente se convierte en el actual. Los otros dos todavía existen. ¿Verdad? Aún no han desaparecido. Muy bien. Entonces, básicamente, estamos creando una pila de estos marcos, ya sabes, estas copias de estos marcos, y cada copia establece un nuevo valor en ese mapa. Muy bien. De acuerdo. Siempre hay un marco de almacenamiento actual. Run almacena una referencia al marco actual, copia el marco actual, establece el nuevo valor, establece ese nuevo marco como actual, ejecuta ese callback, y luego restaura el marco original. Y este code aquí no es la implementación real. Es solo una implementación de seudocódigo que muestra el flujo. De acuerdo. Entonces, cuando se llama a run, esto es el ALS run, obtenemos el marco actual. De acuerdo. Luego copiamos ese marco actual. Establecemos ese valor que en este caso es la instancia de almacenamiento local asíncrono. Lo usamos como clave. Lo establecemos en este valor, y establecemos ese nuevo marco como actual, ejecutamos el callback. Y observa en el finally, por lo tanto, si el callback genera un error o no, si se completa correctamente o si hay una excepción, aún volveremos y restauraremos el marco que era el actual cuando comenzamos la ejecución. De acuerdo. Básicamente, solo estamos intercambiando estas cosas a medida que avanzamos y ejecutamos este code. De acuerdo. Muy bien. ¿Entonces, ¿es realmente solo un mapa? Hay un poco más que eso. Entonces tenemos estos marcos. Como dije, cada vez que llamamos a run, copiamos este marco, copiamos este mapa, agregamos un nuevo valor, lo establecemos como actual, todas estas cosas. Pero eso no da la imagen completa porque también tenemos que saber cómo estamos estableciendo y restableciendo estas cosas para que en cada promesa individual o en cada actividad asíncrona individual que ocurre, se esté utilizando el marco correcto en cualquier momento dado. Así que hablemos un poco sobre cómo funciona una promesa. Estos son los aspectos internos. Y estoy simplificando esto en gran medida a propósito.
7. Funcionamiento interno de Promise
Short description:
Una promesa tiene cuatro valores: estado (pendiente, resuelta o rechazada), resultado (valor o excepción) y reacciones (resolución o rechazo). El resolvedor de la promesa modifica el estado y el programador de la cola de microtareas ejecuta las tareas. El resolvedor establece el estado y el resultado, y pasa las tareas al programador para que se ejecuten.
Pero esto debería dar un poco de comprensión de cómo fluye y qué está sucediendo bajo la superficie. Muy bien. Entonces tenemos una promesa, ¿verdad? Y dentro de una promesa, básicamente tenemos cuatro valores. Hay más que esto, pero como dije, lo estoy simplificando demasiado. Tenemos un estado, que puede ser pendiente, resuelto o rechazado. Tenemos un resultado, que puede ser el valor al que se resolvió o la excepción. Y tenemos un conjunto de reacciones, ya sea reacciones de resolución o reacciones de rechazo. También hay reacciones de finalización, pero las ignoraré por ahora.
Y lo que son, son matrices de tareas, ¿de acuerdo? Para cada una de ellas. Y las tareas son, estas son las cosas que se deben hacer después de que se resuelva. Las cosas que se deben hacer después de que se rechace. Estas son las cosas que se establecen con then o catch o finally, ese tipo de cosas. Muy bien. Entonces la promesa es esa estructura. Además, tenemos este resolvedor de promesas, que es el par de funciones, ya sea la resolución o el rechazo, que modifican ese estado. Y luego tenemos este programador de la cola de microtareas que realmente ejecuta las tareas.
Muy bien. Tenemos un poco de code. Llama a resolve, para resolver esa promesa. Vamos a establecer el estado en resuelto. Vamos a establecer el resultado en el valor que sea, y vamos a tomar esa matriz de tareas en las reacciones de resolución. Y vamos a pasarlas al programador de la cola de microtareas para que se ejecuten. Y luego en algún momento en el futuro, le diremos al programador de la cola de microtareas, oye, ejecuta todas las tareas que tienes, que has recopilado hasta este punto. Y el programador recorrerá y comenzará a iterar a través de cada una de esas tareas y dirá, ok, ejecuta esta, ejecuta esta, ejecuta esta. Muy bien. Ese es un paso importante. ¿De acuerdo? Así que tenemos la promesa, tenemos el resolvedor, tenemos el programador. El resolvedor establece el estado de la promesa, lo actualiza y hace que el programador de la cola de microtareas reciba un conjunto de tareas para ejecutar.
8. Tasks and Promise Continuations
Short description:
Las tareas se utilizan para realizar el siguiente paso en una cadena de promesas. Una tarea es una función con funciones de resolución y rechazo adjuntas. Cuando una tarea representa una continuación de una promesa resuelta, su función de resolución y rechazo se convierten en el resolvedor para la siguiente promesa. Llamar a .then o .catch crea tareas que crean nuevas promesas y las devuelven.
Muy bien. Observa que dice tareas y no funciones. Cuando llamas a .then en una promesa, le estás pasando una función. Cuando llamas a .catch, le estás pasando una función. Y estas son devoluciones de llamada que se revocan. Una tarea no es exactamente lo mismo. Una tarea es un poco más que solo la devolución de llamada. La tarea tiene la función, ¿verdad, y luego tiene una función de resolución y rechazo adjunta a ella. ¿De acuerdo? Y se utiliza para realizar el siguiente paso en una cadena de promesas. ¿De acuerdo? Entonces, cuando el programador recibe esto, va a mirar esto. Esta función de resolución y rechazo es el resolvedor para la siguiente promesa que esta tarea representa. Entonces tomamos una promesa, la resolvimos, y tenemos otra tarea de then como continuación, ¿verdad? Eso también se representa mediante una promesa. Esta función de resolución y rechazo, esta tarea es el resolvedor para esa segunda promesa. Un poco confuso. No necesitamos entrar en todos los detalles aquí. Como dije, estoy simplificando esto bastante solo para desglosarlo. Pero esto es lo que es una tarea, ¿verdad? Es solo una función, una devolución de llamada que resuelve otra promesa. ¿De acuerdo? Entonces, sí, podemos ver eso aquí, función, ejecutar tarea. Esto es lo que haría el programador, ¿de acuerdo? El programador va a ejecutar esta tarea. Va a ejecutar una tras otra. Lo que va a hacer es, ya sabes, dentro de un try-catch, va a intentar ejecutar la función. Si eso tiene éxito, entonces vamos a resolver la siguiente promesa con el resultado. Si eso falla, vamos a rechazar la promesa con el
9. Working with Tasks and Async Context
Short description:
Llamar a then, llamar a catch crea tareas que crean nuevas promesas y las devuelven. Al crear una tarea, se agrega un campo adicional que hace referencia al marco de almacenamiento actual. La tarea se convierte en una función con funciones de resolución, rechazo y una referencia al marco actual. Cuando se ejecuta la tarea, el planificador almacena una referencia al marco actual, establece temporalmente el marco capturado como el actual, ejecuta la función y restaura el marco capturado en el bloque finally.
error. ¿De acuerdo? Muy sencillo. Llamar a then, llamar a catch, como dije, crea estas tareas. Entonces, básicamente esto es lo que está sucediendo dentro de ese then de la promesa o catch de la promesa, creamos otra promesa. Empujamos la reacción en esa, ya sabes, en ese array de reacciones de la promesa. Creamos esta tarea como la función, resolución y rechazo, y luego devolvemos la nueva promesa que se creó. ¿De acuerdo? Muy sencillo, bastante simple. Muy bien. ¿Y qué pasa con el contexto asíncrono? ¿Dónde encaja todo esto? Muy bien. Entonces, lo que hacemos cuando llamamos, nuevamente, esto se refiere a ALS y catch. Cuando queremos crear esa tarea, tenemos que agregar un campo adicional. Y este campo adicional es una referencia a cualquier marco de almacenamiento actual. Muy bien. Como dije, siempre hay un marco actual. Inicialmente, está vacío. Pero cada vez que llamamos a ALS run, creamos un nuevo marco y lo establecemos como el actual. Entonces, cuando llamo a promise.then, va a tomar cualquiera que sea el marco actual, una referencia a eso, y almacenar esa referencia en la tarea misma. Muy bien. Entonces, la tarea ahora es una función, una resolución, un rechazo para la siguiente promesa y una referencia al marco actual. ¿De acuerdo?
Ahora, cuando ejecutamos la tarea, nuevamente, esto está en el planificador. Lo que el planificador va a hacer es decir, bien, quiero obtener el marco actual, cualquiera que sea el marco en este momento, antes de ejecutar esta tarea. Y va a almacenar una referencia a un lado. Y luego va a tomar cualquier marco, cualquier marco de almacenamiento que capture esa tarea, y establecerlo temporalmente como el actual. Muy bien. Aquí es donde tenemos, actual igual a actual marco. Ahí es donde estamos estableciendo el actual a un lado. Lo reemplazamos con cualquier marco que capture la tarea. Luego ejecutamos la función. Muy bien. Y nuevamente, si el resultado tiene éxito, establecemos la resolución de la siguiente promesa como resultado. Si se rechaza, lo rechazamos. Y observa en el finally, así que ya sea que tenga éxito o falle, restauramos el marco actual que se capturó. Entonces, cada vez que ejecutamos una tarea, tomamos el actual, lo reemplazamos, ejecutamos algún code, restauramos el actual. Muy bien. Entonces, cada vez que se ejecuta, estamos reemplazando esto. Entonces, cuando la función de la tarea se ejecuta aquí, va a ver el marco que la tarea
10. Working with ALS Frames
Short description:
El marco de almacenamiento actual se puede intercambiar. Cada llamada a ALS run crea un nuevo marco. Llamar a ALS get recupera el valor del marco actual. Promise then y finally crean nuevas tareas que capturan referencias a los marcos. Los marcos pueden ser recolectados por el recolector de basura y liberados cuando no hay más referencias a ellos.
capturado cuando se llamó a then, o cuando se llamó a catch. Ese es el que se va a ver como el actual. Entonces, básicamente, estamos intercambiando cosas. ¿De acuerdo? Muy bien. Siempre hay un marco de almacenamiento actual. Inicialmente está vacío. Cada llamada a ALS run crea un nuevo marco, que es una copia, más el nuevo valor. Llamar a ALS get solo mira el marco actual. Así que dice lo que sea actual, dame el valor actual para eso, si hay algo. Cada llamada a promise then, finally, crea una nueva tarea que captura una referencia a ella. Cuando se ejecuta una tarea, su marco capturado se establece temporalmente como actual. Y cuando no hay más referencias a una instancia de marco, ese marco puede ser recolectado por el recolector de basura y liberado. Por ejemplo, como dije, inicialmente hay uno vacío. Llamamos a ALS run una vez. Crea un nuevo marco y ejecuta algún code. Si nada en ese code captura una referencia a ese marco, si no creamos otra copia, si no creamos ninguna tarea, no hay nada que mantenga una referencia a él. Es solo otro objeto JavaScript, lo que significa que puede ser recolectado por el recolector de basura y liberado, por lo que no estamos, ya sabes, reteniendo memoria ni nada por el estilo. Entonces, es importante que estoy simplificando demasiado las cosas aquí. Hay mucho más sucediendo bajo la superficie, pero esto te da una idea básica, un nivel alto de cómo funciona todo esto.
11. Working with Set Timeout and Async Context
Short description:
Cuando se llama a set timeout, capturamos una referencia al marco actual y lo restauramos cuando la función es invocada por el temporizador. La implementación de Node es más antigua y más lenta, copiando marcos para cada promesa y recurso asíncrono. Queremos mejorar esto utilizando un modelo similar a otros entornos de ejecución, como los workers. Async Context es una propuesta de TC39 para agregar almacenamiento local asíncrono al lenguaje JavaScript. Actualmente, se puede importar desde Node Async Hooks.
funciona. ¿Qué pasa con algo como set timeout? Set timeout no se basa en promesas. ¿Cómo funciona eso? Bueno, de la misma manera. Cuando se llama a set timeout, capturamos una referencia al marco actual. Cuando esa función es realmente invocada por el temporizador, el temporizador subyacente, restauramos eso, ¿verdad? Pero primero, capturamos el marco actual, lo apartamos, restauramos el marco al que hacemos referencia que obtuvimos cuando llamamos a set timeout, invocamos la función de devolución de llamada y luego restauramos el marco. Entonces, simplemente estamos, ya sabes, todo el tiempo, simplemente intercambiando estas referencias para que el obtener el marco actual siempre refleje el correcto. Muy bien. Node hace las cosas de manera un poco diferente. La implementación de Node, es más antigua. Utiliza los APIs de ganchos asíncronos y ganchos de promesas. Es mucho más lento. Los marcos terminan siendo copiados cada vez que se crea una promesa y cada vez que se crea un recurso asíncrono, como un temporizador o un next tick, en lugar de cada vez que se llama a ALS run. Y puedes imaginar que en una aplicación que está creando decenas o cientos de miles de promesas, copiar este marco cada vez que se crea una promesa se vuelve muy costoso desde el punto de vista del rendimiento. Estamos buscando mejorar esto. Otros entornos de ejecución, como los workers, utilizan un modelo más cercano a lo que describí aquí, donde copiamos cada vez que se invoca ALS. Queremos hacer que la implementación de Node sea así. Y estamos utilizando una API oscura de V8 que se utiliza en los workers, utilizada en Chromium, que hace todo esto mucho más fácil, y será la base de algunas de las cosas nuevas que están por venir. Voy a hablar de eso en un momento. Entonces, la implementación de Node funciona un poco diferente. Es un poco más lenta. Pero queremos cambiarla. Queremos actualizarla y básicamente modernizarla. ¿Qué es Async Context? Async Context es una nueva propuesta de TC39. TC39 es el comité que estandariza el lenguaje JavaScript. La propuesta básicamente consiste en agregar almacenamiento local asíncrono al lenguaje en sí, en lugar de ser una API no estándar. Pero están cambiándolo. Están cambiando el nombre. Algunos de los detalles cambiarán. Entonces, actualmente, para usar el almacenamiento local asíncrono, lo importas desde Node Async Hooks, y esto es
12. Using Async Context with Async Local Storage
Short description:
Con Async Context, ya no tienes que importarlo. Se convierte en un global. Get store ahora es get, y request ID run sigue siendo el mismo. Hay algunas otras diferencias con la API de Async Local Storage, pero estos son los puntos clave. Simplemente toma el marco, modifica el valor copiándolo y cámbialos.
sin importar la plataforma en la que estés, sin importar el tiempo de ejecución en el que estés. Accedes de la misma manera. Estás importándolo desde este módulo de Node Async Hooks. Lo creas con el nuevo constructor de Async Local Storage. Tienes get store run, ¿verdad? Con Async Context, en la mayoría de los casos, va a ser un reemplazo directo. Algunos detalles cambian. No tienes que importarlo. Será un global. Get store simplemente se convierte en get, ¿verdad? Entonces, request ID get. Request ID run sigue siendo el mismo, exactamente de la misma manera. Si observamos las dos, hay diferencias muy mínimas entre ellas. Hay algunas otras diferencias con la API de Async Local Storage, algunas cosas que Async Context no recogerá. No voy a entrar en esos detalles ahora. No son tan importantes y son características experimentales en Async Local Storage de todos modos, así que no vale la pena profundizar. Estas son las piezas que realmente necesitas entender. De acuerdo, y eso es todo. Así que espero que eso realmente haya ayudado. Solo quiero revelar un poco lo que está sucediendo detrás de escena con Async Local Storage. Nuevamente, solo toma el marco. Modificamos el valor copiándolo. Simplemente intercambiamos esas cosas, y sí, espero que
QnA
Adoption and Passing ALS Instance
Short description:
El 64% de las personas no ha utilizado Async Local Storage antes, lo cual no es sorprendente. Es una API de la que la mayoría de las personas no están al tanto. La adopción de Async Local Storage parece ser bastante alta. Una pregunta común es cómo pasar la instancia de ALS a lo largo de la pila de llamadas sin tener que pasarla directamente. Diferentes estrategias incluyen declararla en un ámbito de nivel superior o usar un módulo como dependencia. Estos enfoques ayudan a evitar pasarla directamente.
La ayuda. Espero que disfrutes el resto de tu conferencia. En primer lugar, echemos un vistazo a la pregunta de la encuesta que publicaste para la audiencia antes. La pregunta fue: ¿Has utilizado Async Local Storage en tu aplicación antes? El 64% de las personas respondió que no. ¿Te sorprende mucho? No, no es una gran sorpresa en absoluto. Es una de esas API en las que la mayoría de las personas no van a pensar. No van a saber realmente que está ahí. Me habría sorprendido mucho más si esos números se hubieran invertido. Sí, además, esa no era la segunda pregunta en mi mente. Es como, bueno, ¿cuántas personas lo han usado antes, pero no sabían que lo usaban? Probablemente bastantes. Eso, bastantes. Ahora que se está incorporando en varios frameworks y cosas así, probablemente sea mucho más común encontrarlo en el fondo. Sí, de hecho, estoy sorprendido de que sea un tercio, dos tercios, un tercio lo haya usado antes, por lo que la adopción parece ser bastante alta. Echemos un vistazo a las preguntas que ustedes enviaron para James. Muchas gracias. Voy a ir directamente a la primera pregunta. ¿Cómo sugieres pasar la instancia de ALS al code que lo necesita a lo largo de la pila de llamadas? Todavía necesitas pasarla, con suerte, sin tener que pasarla directamente. Sí, sí. Esta es en realidad una de las preguntas más comunes sobre ALS, así que estoy muy contento de ver que esta llegó. Está claro que en realidad no resuelve todo el problema. ¿Cómo haces esto, no vas a pasar el ID de registro o el ID de solicitud, pero aún tienes que pasar algo. Todos los ejemplos van a mostrar que esto se declara como un ámbito de nivel superior al que cualquier code que necesite acceder a él, ya sea para establecer el valor o obtener el valor, necesita ver. Puedes establecerlo en el ámbito global, puedes establecerlo en el nivel superior. Cuando digo nivel superior, me refiero a la diferencia entre eso y el global, por ejemplo, si es una variable declarada en la parte superior de una función, y cualquier cosa dentro de ella lo cierra, o si está en algún objeto de contexto que está disponible en todas partes. Solo tiene que estar en algún nivel en el que cualquier cosa que lo necesite pueda verlo. Otra estrategia que he visto es usar un módulo para ello, donde puedes importar esa instancia de ALS desde algo como una dependencia. Cualquier otro módulo que lo necesite puede hacer lo mismo. Importan eso, obtienen acceso a él y pueden establecer el valor. Hay varias estrategias diferentes. Como dije, no resuelve todos los aspectos, pero al menos te da parte de la solución para evitar pasar las cosas. Gracias. Muchas gracias.
Memory Leaks, Performance, and Understanding APIs
Short description:
Almacenar ALS en el ámbito global puede provocar fugas de memoria. Evita usar ALS en Node.js debido a problemas de rendimiento con los ganchos asíncronos. Comprender cómo funcionan las APIs internamente es crucial para resolver problemas y solucionar errores como las fugas de memoria. Es importante mirar dentro de las cajas negras y recordar nuestras capacidades técnicas.
La siguiente pregunta es, ¿hay alguna precaución con las fugas de memoria con ALS? Sí. Con cualquier cosa. Si estás almacenando un valor y va a estar allí, digamos, si lo estás colocando en el ámbito global, y ejecutas esto, cualquier cosa que mantenga una referencia a esa instancia de ALS, como nuevamente, si la estás almacenando en global this, eso significa que es probable que ese valor se retenga mucho, mucho más tiempo de lo que habías anticipado. No hay forma de deshacer eso. En la implementación de los workers, este marco que tenemos que se mencionó en la charla es básicamente un mapa. Copiamos ese mapa cada vez que establecemos un valor. Contamos las referencias internamente para que cuando nada más lo esté apuntando, nada más lo necesite, y ese recuento de referencias caiga a cero para esa copia en particular, podemos liberarlo, el recolector de basura puede entrar en acción y recuperar la memoria. Pero si una implementación no es cuidadosa, es posible, por ejemplo, que ALS capture una referencia a sí mismo, en cuyo caso, eso simplemente se quedará en memoria y se colocará rápidamente en el espacio antiguo. Hay que tener cuidado. Tengo que estar atento.
¿Recomendarías evitar ALS en Node.js hasta que se resuelvan algunos de los problemas de rendimiento? Desafortunadamente, sí. La implementación que existe se basa en ganchos asíncronos, y los ganchos asíncronos son muy costosos de activar. Queremos solucionar esta implementación. Bradley Fryas ha estado trabajando en esto basado en la implementación que se está utilizando en V8 para el contexto asíncrono, y basado en lo que hicimos en los workers, simplemente modelando después de eso, sería mucho, mucho más rápido. Vale la pena usarlo, pero ten en cuenta que podrías experimentar hasta un 30% de disminución en el rendimiento solo al activar los ganchos asíncronos y usar esto, así que debes tener cuidado. Correcto. Por ahora, úsalo en entornos de ejecución como los workers de CloudFlare, donde ese tipo de cosas está optimizado. Sí, o en lugares donde el impacto en el rendimiento puede no ser tan devastador, ¿verdad? Hay algunos escenarios de servicio donde una disminución del 30% simplemente no es aceptable. Bien, como última pregunta, y tal vez puedas proporcionarnos una respuesta rápida aquí, ¿por qué es importante entender cómo funcionan estas APIs internamente? ¿No es suficiente con saber que simplemente funcionan? Debes conocer cómo funcionan estas cosas internamente para poder resolver problemas, solucionar errores como el problema de la fuga de memoria, ¿verdad? Si no sabes cómo funciona internamente, nunca entenderás cómo solucionar esa fuga de memoria y por qué la fuga está ahí en realidad. Para mí, entender los detalles internos, todo lo que sucede bajo la superficie, es esencial para escribir un buen código. También siento que con cada vez más cajas negras, con las que tenemos que interactuar todos los días, no está de más echar un vistazo de vez en cuando dentro de una caja negra y recordarnos que de hecho somos personas técnicamente capacitadas. Somos capaces de entender incluso cosas muy profundas. Jeff, muchas gracias por esta increíble charla, y muchas gracias por tus contribuciones a la comunidad de Node.js. Es genial tenerte aquí. Sí, gracias por invitarme. Ha sido divertido.
Today's Talk introduces TRPC, a library that eliminates the need for code generation and provides type safety and better collaboration between front-end and back-end. TRPC is demonstrated in a Next JS application integrated with Prisma, allowing for easy implementation and interaction with the database. The library allows for seamless usage in the client, with automatic procedure renaming and the ability to call methods without generating types. TRPC's client-server interaction is based on HTTP requests and allows for easy debugging and tracing. The library also provides runtime type check and validation using Zod.
GraphQL has made a huge impact in the way we build client applications, websites, and mobile apps. Despite the dominance of resolvers, the GraphQL specification does not mandate their use. Introducing Graphast, a new project that compiles GraphQL operations into execution and output plans, providing advanced optimizations. In GraphFast, instead of resolvers, we have plan resolvers that deal with future data. Graphfast plan resolvers are short and efficient, supporting all features of modern GraphQL.
This Talk discusses handling breaking changes in a GraphQL schema, including the use of the deprecated directive to tag fields that should no longer be used. It also covers the process of deploying GraphQL APIs and mobile apps, highlighting the challenges of mobile app release adoption. The Talk emphasizes the importance of making safe upgrades in mobile apps and provides strategies for detecting and handling breaking changes, such as using TypeScript and GraphQL Inspector. Overall, the Talk emphasizes the need to minimize user impact when introducing breaking changes in GraphQL schemas.
This Talk covers advanced patterns for API management in large-scale React applications. It introduces the concept of an API layer to manage API requests in a more organized and maintainable way. The benefits of using an API layer include improved maintainability, scalability, flexibility, and code reusability. The Talk also explores how to handle API states and statuses in React, and provides examples of canceling requests with Axios and React Query. Additionally, it explains how to use the API layer with React Query for simplified API management.
This Talk discusses the safe handling of dynamic data with TypeScript using JSON Schema and TypeBox. Fastify, a web framework, allows developers to validate incoming data using JSON schema, providing type safety and error handling. TypeBox is a powerful library that allows developers to define JSON schemas and derive static types in TypeScript. The combination of JSON schema, TypeBox, and Fastify provides powerful tools for type safety and validation of dynamic data.
Technology is a spiral, with foundational ideas resurfacing. Java revolutionized enterprise applications. REST and JSON simplified building APIs and websites. Node.js enabled fast and custom development, leading to the microservices revolution. Platformatic aims to fill the gap in building, managing, and scaling microservices painlessly.
Comienza con la Rejilla de Datos React de AG Grid con un tutorial práctico del equipo central que te llevará a través de los pasos para crear tu primera rejilla, incluyendo cómo configurar la rejilla con propiedades simples y componentes personalizados. La edición comunitaria de AG Grid es completamente gratuita para usar en aplicaciones comerciales, por lo que aprenderás una herramienta poderosa que puedes agregar inmediatamente a tus proyectos. También descubrirás cómo cargar datos en la rejilla y diferentes formas de agregar renderizado personalizado a la rejilla. Al final de la masterclass, habrás creado una Rejilla de Datos React de AG Grid y personalizado con componentes React funcionales.- Comenzando e instalando AG Grid- Configurando ordenación, filtrado, paginación- Cargando datos en la rejilla- La API de la rejilla- Usando hooks y componentes funcionales con AG Grid- Capacidades de la edición comunitaria gratuita de AG Grid- Personalizando la rejilla con Componentes React
The Graph es un protocolo de indexación para consultar redes como Ethereum, IPFS y otras blockchains. Cualquiera puede construir y publicar APIs abiertas, llamadas subgrafos, para hacer que los datos sean fácilmente accesibles.
En este masterclass aprenderás cómo construir un subgrafo que indexa datos de blockchain de NFT del contrato inteligente Foundation. Desplegaremos la API y aprenderemos cómo realizar consultas para recuperar datos utilizando diferentes tipos de patrones de acceso a datos, implementando filtros y ordenamiento.
Al final del masterclass, deberías entender cómo construir y desplegar APIs de alto rendimiento en The Graph para indexar datos de cualquier contrato inteligente desplegado en Ethereum.
Prisma es un ORM de código abierto para Node.js y TypeScript. En esta masterclass, aprenderás los flujos de trabajo fundamentales de Prisma para modelar datos, realizar migraciones de base de datos y consultar la base de datos para leer y escribir datos. También aprenderás cómo Prisma se integra en tu stack de aplicaciones, construyendo una API REST y una API GraphQL desde cero utilizando SQLite como base de datos. Tabla de contenidos: - Configuración de Prisma, modelado de datos y migraciones- Explorando Prisma Client para consultar la base de datos- Construyendo rutas de API REST con Express- Construyendo una API GraphQL con Apollo Server
Tabla de contenidos: - Desplegando una API GraphQL predefinida - Creando un worker en el borde + caché en Cloudflare - Configurando la caché basada en los nombres de tipo - Agregando invalidación basada en los tipos de retorno de mutación
Aprenda cómo generar APIs GraphQL instantáneas utilizando un conector de fuente de datos (fuentes GraphQL y no GraphQL), extenderlas y unirlas ambas con resolutores personalizados y desplegar al borde sin salir del editor de código.
Con el surgimiento de frameworks, como React, Vue o Angular, la forma en que se construyen los sitios web ha cambiado a lo largo de los años. Las aplicaciones modernas pueden ser muy dinámicas y realizar múltiples solicitudes de API para poblar un sitio web con contenido actualizado o enviar nuevos datos a un servidor. Sin embargo, este cambio de paradigma ha introducido nuevos problemas con los que los desarrolladores deben lidiar. Cuando una solicitud de API está pendiente, tiene éxito o falla, el usuario debe recibir una retroalimentación significativa. Otros problemas pueden incluir el almacenamiento en caché de datos de API o la sincronización del estado del cliente con el servidor. Todos estos problemas requieren soluciones que deben ser codificadas, pero pueden volverse rápidamente inmanejables y dar como resultado una base de código difícil de ampliar y mantener. En este masterclass, cubriremos cómo manejar las solicitudes de API, los estados de API y la cancelación de solicitudes mediante la implementación de una Capa de API y combinándola con React-Query. Prerrequisitos: Para aprovechar al máximo este masterclass, debes estar familiarizado con React y Hooks, como useState, useEffect, etc. Si deseas codificar junto con nosotros, asegúrate de tener Git, un editor de código, Node y npm instalados en tu máquina.
Comments