En Javascript, tradicionalmente utilizabamos var
para declarar elementos y variables, pero con la llegada de ECMAScript 2015 se trasladó a utilizar const
y let
. Recientemente, llega una forma nueva de declarar elementos mediante la palabra clave using
.
La diferencia respecto a los anteriores, es que using
permite gestionar de forma controlada los recursos, pudiendo lanzar tareas de limpieza o gestión cuando se destruye el elemento al llegar a su final de ámbito.
La palabra clave using
Veamos un ejemplo sencillo para empezar. Observa las dos formas de declarar un elemento en Javascript. Ambas son válidas y funcionan correctamente. Estoy utilizando new URL()
para declarar una URL, pero esto no es importante, no es el foco del asunto, es simplemente un ejemplo:
const element = new URL("https://manz.dev/");
using element = new URL("https://manz.dev/");
Este fragmento de código funcionará perfectamente y a priori no observarás diferencias, ya que los elementos son globales y siempre están disponibles en el ámbito global. Ahora, vamos a meter esta declaración con using
en un ámbito limitado (dentro de las llaves), para forzar a que esa variable se destruya al terminar su ámbito.
En este ejemplo, yo lo he hecho simplemente con llaves, pero podría hacerse en un ámbito de un bucle for
, de un condicional if
, etc:
{
using element = new URL("https://manz.dev/");
// Aquí termina la existencia de "element"
}
// Aquí "element" ya no existe
Esta limitación de ámbitos tampoco tiene ninguna diferencia a hacerlo con const
o let
. En ambos casos la variable element
deja de existir al finalizar las llaves y fuera de ese ámbito no existe.
Pero con using
si intentamos ejecutar este fragmento de código obtendremos el siguiente error:
Uncaught TypeError: Symbol(Symbol.dispose) is not a function
Este error ocurre porque al utilizar using
el sistema espera que exista una función con nombre Symbol.dispose
en ese elemento, que es la que se ejecutará automáticamente al destruir el elemento. En este ejemplo, no existe tal función, y por eso muestra ese error.
Si no conoces los Símbolos en Javascript, te recomiendo que los leas antes de continuar.
La función de limpieza
La función de limpieza es una función que debemos implementar nosotros. No existe por defecto, sino que using
la va a ejecutar al finalizar la vida de la variable (como vimos antes, donde daba error porque no estaba implementada). Esta función se utilizará para realizar tareas adicionales o de limpieza.
Veamos como implementarla.
El símbolo Symbol.dispose
Vamos a ampliar un poco nuestro ejemplo anterior, añadiéndole un Symbol.dispose
como debe ser en cualquier elemento donde utilicemos using
. Observa que al necesitar añadir varios elementos en el objeto, no puede ser simple, por lo que nuestro elemento va a pasar a tener dos propiedades.
Piensa en el siguiente modelo mental:
const element = {
value: /* ... */,
dispose: /* ... */
};
En nuestro nuevo ejemplo, haremos algunos cambios en ese modelo mental:
- 1️⃣ En lugar de utilizar
const
utilizaremosusing
- 2️⃣ Nuestra información, pasará a estar dentro de la propiedad
value
- 3️⃣ En lugar de utilizar
dispose
, utilizaremos[Symbol.dispose]
.
Nuestro ejemplo anterior quedaría como se ve en el siguiente fragmento de código:
{
using element = {
value: new URL("https://google.com/"),
[Symbol.dispose]: () => console.log("Liberando recursos...")
}
console.log(element.value);
}
Observa que [Symbol.dispose]
es una propiedad donde se guarda una función. Dicha función se va a ejecutar automáticamente cuando llegue al final de su vida y se destruya el elemento, actuando como una especie de vigilante que estará diseñado para ejecutar funciones orientadas a liberar recursos o limpiar información que no va a ser necesaria más adelante.
Si te confunde la sintaxis de
[Symbol.dispose]
o te parece rara, simplemente imagina que esa parte esdispose
, pero con una sintaxis diferente porque es una situación especial.
El símbolo Symbol.asyncDispose
De la misma forma que utilizamos [Symbol.dispose]
en el ejemplo anterior, puede ser necesario querer utilizar operaciones asíncronas en la función de limpieza, como por ejemplo, acceder mediante un fetch()
a una API con información necesaria para la limpieza.
En ese caso, haremos los siguientes cambios:
- 1️⃣ En lugar de
using
utilizaremosawait using
- 2️⃣ En lugar de
[Symbol.dispose]
utilizaremos[Symbol.asyncDispose]
- 3️⃣ Le añadiremos el
async
a la función de limpieza
El código quedaría como sigue:
{
await using element = {
value: new URL("https://google.com/"),
[Symbol.asyncDispose]: async () => console.log("Liberando recursos...")
}
console.log(element.value);
}
Ahora podremos hacer uso de operaciones asíncronas en su interior.
Uso de clases OOP
Obviamente, en estos ejemplos hemos utilizado un simple
class DisposableURL {
url;
constructor(value) {
this.url = new URL(value);
}
[Symbol.dispose]() {
console.log("Liberando recursos...");
}
}
Una vez declarada nuestra clase, ahora podemos utilizar la siguiente sintaxis, mucho más limpia y clara:
{
using element = new DisposableURL("https://manz.dev/");
console.log(element.url);
}
Piensa en estas funciones de limpieza como una función que se encarga de eliminar grandes estructuras de datos relacionadas y que ya no harán falta, cerrar conexiones a bases de datos que dejan de hacer falta, etc. Tareas adicionales que pueden mejorar el rendimiento de nuestra aplicación web.