Comprendiendo el Contexto Asíncrono

Rate this content
Bookmark

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?

James Snell
James Snell
29 min
04 Apr, 2024

Comments

Sign in or register to post your comment.

Video Summary and Transcription

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.

Available in English

1. Introducción a async local storage

Short description:

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.

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

Get rid of your API schemas with tRPC
React Day Berlin 2022React Day Berlin 2022
29 min
Get rid of your API schemas with tRPC
Do you know we can replace API schemas with a lightweight and type-safe library? With tRPC you can easily replace GraphQL or REST with inferred shapes without schemas or code generation. In this talk we will understand the benefit of tRPC and how apply it in a NextJs application. If you want reduce your project complexity you can't miss this talk.
Step aside resolvers: a new approach to GraphQL execution
GraphQL Galaxy 2022GraphQL Galaxy 2022
16 min
Step aside resolvers: a new approach to GraphQL execution
Though GraphQL is declarative, resolvers operate field-by-field, layer-by-layer, often resulting in unnecessary work for your business logic even when using techniques such as DataLoader. In this talk, Benjie will introduce his vision for a new general-purpose GraphQL execution strategy whose holistic approach could lead to significant efficiency and scalability gains for all GraphQL APIs.
Handling Breaking Changes in GraphQL
GraphQL Galaxy 2022GraphQL Galaxy 2022
22 min
Handling Breaking Changes in GraphQL
Top Content
Requirements change, but API contracts are forever - I wish! A breaking change is something that is not backwards compatible. This means that a consumer of your API would not be able to use the new version without making a code change themselves. We avoid breaking changes if possible, but there are cases when they are inevitable. It could be something small: like making a mandatory field optional. Or it could be something big: like removing a query or a mutation. In this talk we'll review the types of breaking changes you may encounter and how to deal with them gracefully.
Advanced Patterns for API Management in Large-Scale React Applications
React Advanced Conference 2021React Advanced Conference 2021
20 min
Advanced Patterns for API Management in Large-Scale React Applications
Top Content
In this talk, you will discover how to manage async operations and request cancellation implementing a maintainable and scalable API layer and enhancing it with de-coupled cancellation logic. You will also learn how to handle different API states in a clean and flexible manner.
Safely Handling Dynamic Data with TypeScript
Node Congress 2021Node Congress 2021
29 min
Safely Handling Dynamic Data with TypeScript
TypeScript makes JavaScript safer adding static type definitions. Static definitions are wonderful; they prevent developers from making trivial mistakes ensuring every assignment and invocation is done correctly. A variable typed as a string cannot be assigned a number, and a function expecting three arguments cannot be called with only two. These definitions only exist at build time though; the code that is eventually executed is just JavaScript. But what about the response from an API request? In this talk Ethan Arrowood, Software Engineer 2 @ Microsoft, he will cover various solutions for safely typing dynamic data in TypeScript applications. This talk features popular technologies such as Fastify, JSON Schema, Node.js, and more!
APIs are Evolving. Again.
JSNation 2023JSNation 2023
28 min
APIs are Evolving. Again.
As developers we stand on the shoulders of giants, and it can be helpful to take a look at the past to gain a better perspective. In this talk we’ll briefly explore the past decade of backend development and architectural patterns.
We’ve often ditched technologies in an attempt to make the developer experience frictionless. However we sometimes forget what we can learn from “the good old days”.
What are you building: a monolith, a microservices system or something in between? A shift in how we see things can help us keep moving forwards.

Workshops on related topic

Building GraphQL APIs on top of Ethereum with The Graph
GraphQL Galaxy 2021GraphQL Galaxy 2021
48 min
Building GraphQL APIs on top of Ethereum with The Graph
WorkshopFree
Nader Dabit
Nader Dabit
The Graph is an indexing protocol for querying networks like Ethereum, IPFS, and other blockchains. Anyone can build and publish open APIs, called subgraphs, making data easily accessible.

In this workshop you’ll learn how to build a subgraph that indexes NFT blockchain data from the Foundation smart contract. We’ll deploy the API, and learn how to perform queries to retrieve data using various types of data access patterns, implementing filters and sorting.

By the end of the workshop, you should understand how to build and deploy performant APIs to The Graph to index data from any smart contract deployed to Ethereum.
Hands-on with AG Grid's React Data Grid
React Summit 2022React Summit 2022
147 min
Hands-on with AG Grid's React Data Grid
WorkshopFree
Sean Landsman
Sean Landsman
Get started with AG Grid React Data Grid with a hands-on tutorial from the core team that will take you through the steps of creating your first grid, including how to configure the grid with simple properties and custom components. AG Grid community edition is completely free to use in commercial applications, so you'll learn a powerful tool that you can immediately add to your projects. You'll also discover how to load data into the grid and different ways to add custom rendering to the grid. By the end of the workshop, you will have created an AG Grid React Data Grid and customized with functional React components.- Getting started and installing AG Grid- Configuring sorting, filtering, pagination- Loading data into the grid- The grid API- Using hooks and functional components with AG Grid- Capabilities of the free community edition of AG Grid- Customizing the grid with React Components
Database Workflows & API Development with Prisma
Node Congress 2022Node Congress 2022
98 min
Database Workflows & API Development with Prisma
WorkshopFree
Nikolas Burk
Nikolas Burk
Prisma is an open-source ORM for Node.js and TypeScript. In this workshop, you’ll learn the fundamental Prisma workflows to model data, perform database migrations and query the database to read and write data. You’ll also learn how Prisma fits into your application stack, building a REST API and a GraphQL API from scratch using SQLite as the database.
Table of contents:
- Setting up Prisma, data modeling & migrations- Exploring Prisma Client to query the database- Building REST API routes with Express- Building a GraphQL API with Apollo Server
Best Practices and Patterns for Managing API Requests and States
React Advanced Conference 2022React Advanced Conference 2022
206 min
Best Practices and Patterns for Managing API Requests and States
Workshop
Thomas Findlay
Thomas Findlay
With the rise of frameworks, such as React, Vue or Angular, the way websites are built changed over the years. Modern applications can be very dynamic and perform multiple API requests to populate a website with fresh content or submit new data to a server. However, this paradigm shift introduced new problems developers need to deal with. When an API request is pending, succeeds, or fails, a user should be presented with meaningful feedback. Other problems can comprise API data caching or syncing the client state with the server. All of these problems require solutions that need to be coded, but these can quickly get out of hand and result in a codebase that is hard to extend and maintain. In this workshop, we will cover how to handle API requests, API states and request cancellation by implementing an API Layer and combining it with React-Query.
Prerequisites: To make the most out of this workshop, you should be familiar with React and Hooks, such as useState, useEffect, etc. If you would like to code along, make sure you have Git, a code editor, Node, and npm installed on your machine.
Building GraphQL APIs With The Neo4j GraphQL Library
GraphQL Galaxy 2021GraphQL Galaxy 2021
175 min
Building GraphQL APIs With The Neo4j GraphQL Library
WorkshopFree
William Lyon
William Lyon
This workshop will explore how to build GraphQL APIs backed Neo4j, a native graph database. The Neo4j GraphQL Library allows developers to quickly design and implement fully functional GraphQL APIs without writing any resolvers. This workshop will show how to use the Neo4j GraphQL Library to build a Node.js GraphQL API, including adding custom logic and authorization rules.

Table of contents:
- Overview of GraphQL and building GraphQL APIs
- Building Node.js GraphQL APIs backed a native graph database using the Neo4j GraphQL Library
- Adding custom logic to our GraphQL API using the @cypher schema directive and custom resolvers
- Adding authentication and authorization rules to our GraphQL API