La gestión de recursos ha sido una fuente constante de errores en aplicaciones JavaScript y TypeScript. Con la llegada del keyword using, los desarrolladores obtienen una herramienta nativa para asegurar la correcta liberación de recursos, similar a lo que ya existe en lenguajes como C#.

Visión general del keyword `using`

Introducido en TypeScript 5.2, el keyword using permite declarar bloques que garantizan la ejecución del método dispose al final de su alcance. Esto brinda una forma segura y concisa de manejar recursos como conexiones a bases de datos, streams o cualquier objeto que implemente la interfaz Disposable.

Sintaxis y ejemplos básicos

El patrón básico consiste en envolver la creación del recurso dentro de una declaración using. Al salir del bloque, TypeScript llama automáticamente a dispose.

interface Disposable {
  dispose(): void;
}

class FileHandle implements Disposable {
  constructor(public path: string) {
    console.log('Abriendo archivo', path);
  }
  read() {
    console.log('Leyendo archivo');
  }
  dispose() {
    console.log('Cerrando archivo', this.path);
  }
}

function procesarArchivo(ruta: string) {
  using const archivo = new FileHandle(ruta);
  archivo.read();
  // Al terminar el bloque, archivo.dispose() se llama automáticamente
}

procesarArchivo('datos.txt');

Observe que el recurso se declara con using const, lo que indica que el alcance del recurso es limitado al bloque donde se declara.

Patrones avanzados de gestión de recursos

Más allá del caso simple, es posible combinar using con estructuras asíncronas. TypeScript permite el uso de await using cuando el método dispose devuelve una Promise.

class AsyncConnection implements Disposable {
  async connect() { console.log('Conectando...'); }
  async query(sql: string) { console.log('Ejecutando', sql); }
  async dispose() { console.log('Desconectando...'); }
}

async function ejecutarConsulta() {
  await using const conn = new AsyncConnection();
  await conn.connect();
  await conn.query('SELECT * FROM usuarios');
  // conn.dispose() se espera implícitamente al salir del bloque
}

await ejecutarConsulta();

Este patrón garantiza que incluso las operaciones asíncronas liberen sus recursos sin necesidad de bloques try...finally explícitos.

Integración en bases de código existentes

Para adoptar using en proyectos legacy, lo ideal es envolver los objetos que ya implementan dispose o crear adaptadores. Un adaptador típico convierte objetos de la API de fs en un Disposable:

import { createReadStream } from 'fs';

class StreamDisposable implements Disposable {
  constructor(private stream: NodeJS.ReadableStream) {}
  dispose() { this.stream.destroy(); console.log('Stream destruido'); }
}

function leerArchivo(path: string) {
  using const archivo = new StreamDisposable(createReadStream(path));
  archivo.stream.on('data', chunk => console.log('Datos:', chunk.toString()))
  // No se necesita close manual
}

leerArchivo('log.txt');

Al migrar gradualmente, los equipos pueden combinar bloques tradicionales con using, manteniendo la compatibilidad mientras reducen la superficie de error.

Conclusión

El keyword using representa un paso importante hacia una gestión de recursos más segura en el ecosistema TypeScript. Su adopción reduce la necesidad de try...finally, simplifica el código asíncrono y ayuda a prevenir fugas de memoria que suelen pasar desapercibidas en entornos de larga ejecución.

Referencias