Using: Limpieza de recursos

Declarando recursos controlados


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 no es importante, 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 no observarás diferencias, ya que los elementos son globales y siempre están disponibles en un á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 destruye 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

Si intentamos ejecutar este código, ahora si, 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á 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

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 con 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 utilizaremos using
  • 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 es dispose, 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 utilizaremos await 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 para hacer lo más simple nuestro ejemplo. Sin embargo, es posible utilizar cualquier tipo de estructura, como por ejemplo una clase de OOP, donde probablemente sea mucho más cómodo y organizado:

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);
}

¿Quién soy yo?

Soy Manz, vivo en Tenerife (España) y soy streamer partner en Twitch y profesor. Me apasiona el universo de la programación web, el diseño y desarrollo web y la tecnología en general. Aunque soy full-stack, mi pasión es el front-end, la terminal y crear cosas divertidas y locas.

Puedes encontrar más sobre mi en Manz.dev