using e await using: Gerenciamento Explícito de Recursos em JavaScript

Como as novas declarações using e await using do JavaScript liberam arquivos, conexões e locks automaticamente ao sair de escopo, eliminando o try/finally manual de limpeza de recursos.

Fechar um arquivo, encerrar uma conexão, cancelar um listener, liberar um lock: toda vez que um recurso precisa ser limpo depois de usado, o JavaScript sempre exigiu um try/finally manual. As declarações using e await using, do Gerenciamento Explícito de Recursos (ES2026), automatizam essa limpeza, chamando o método de descarte do recurso assim que ele sai de escopo — de forma parecida com o with do Python ou o using do C#.

O problema: limpeza manual com try/finally

function lerConfiguracao(caminho) {
    const arquivo = abrirArquivo(caminho);
    try {
        return processar(arquivo);
    } finally {
        arquivo.fechar();
    }
}

Funciona para um único recurso, mas cresce rápido: cada recurso adicional exige mais um nível de aninhamento de try/finally, e é fácil esquecer a limpeza em um caminho de erro que ninguém testou.

Como funciona o using

Uma variável declarada com using precisa ser null, undefined ou um objeto com um método [Symbol.dispose](). Esse método é chamado automaticamente quando a execução sai do bloco em que a variável foi declarada — em ordem inversa à declaração, e mesmo que uma exceção seja lançada no meio do caminho:

class Arquivo {
    constructor(caminho) {
        this.caminho = caminho;
        console.log(`Abrindo ${caminho}`);
    }

    [Symbol.dispose]() {
        console.log(`Fechando ${this.caminho}`);
    }
}

function lerConfiguracao(caminho) {
    using arquivo = new Arquivo(caminho);
    return processar(arquivo);
    // [Symbol.dispose]() é chamado aqui, ao sair do escopo da função,
    // tanto no retorno normal quanto se processar() lançar uma exceção.
}

Não existe mais try/finally explícito: a limpeza está amarrada à declaração da variável, não a um bloco separado que alguém precisa lembrar de escrever.

await using para recursos assíncronos

Quando o descarte do recurso precisa ser assíncrono (fechar uma conexão de rede, liberar um lock remoto), o objeto implementa [Symbol.asyncDispose]() em vez de [Symbol.dispose](), e a declaração usa await using. O await garante que a limpeza termine antes do código continuar:

class ConexaoBanco {
    static async conectar(url) {
        const conexao = new ConexaoBanco(url);
        await conexao.abrir();
        return conexao;
    }

    async [Symbol.asyncDispose]() {
        await this.fechar();
        console.log('Conexão encerrada');
    }
}

async function consultarUsuarios() {
    await using conexao = await ConexaoBanco.conectar('postgres://...');
    return conexao.query('SELECT * FROM usuarios');
    // A conexão só é considerada fechada depois que
    // [Symbol.asyncDispose]() resolver.
}

Múltiplos recursos no mesmo escopo

Vários using no mesmo bloco são descartados na ordem inversa em que foram declarados — o último aberto é o primeiro fechado, igual a uma pilha:

function processarLote() {
    using lockFila = adquirirLock('fila');
    using lockCache = adquirirLock('cache');

    return executar();
    // Descarte automático: lockCache primeiro, depois lockFila.
}

Essa ordem importa quando um recurso depende do outro ainda estar aberto durante o próprio descarte, como um lock secundário que registra sua liberação no recurso principal.

DisposableStack: agrupando descartes dinâmicos

Quando o número de recursos só é conhecido em tempo de execução, o DisposableStack (e sua versão assíncrona AsyncDisposableStack) permite empilhar funções de limpeza avulsas e descartar tudo de uma vez:

function configurarListeners(elementos) {
    using pilha = new DisposableStack();

    for (const elemento of elementos) {
        const handler = () => console.log('clique em', elemento.id);
        elemento.addEventListener('click', handler);
        pilha.defer(() => elemento.removeEventListener('click', handler));
    }

    return () => pilha.dispose();
}

O próprio DisposableStack implementa [Symbol.dispose](), então ele mesmo pode ser usado com using, e cada função registrada com defer() roda na hora do descarte.

Quando usar

Cuidados

using e await using se comportam como const: a variável precisa ser inicializada na declaração e não pode ser reatribuída depois. Além disso, o descarte é amarrado ao escopo de bloco, não ao tempo de vida do objeto — se a referência escapar do bloco (por exemplo, sendo retornada), o recurso já terá sido descartado quando o chamador tentar usá-lo.

Compatibilidade

O suporte a using e await using já está disponível em navegadores baseados em Chromium e no Node.js, com Firefox seguindo logo atrás; o Safari ainda não implementou a proposta. Em projetos que usam TypeScript ou um bundler como Vite, Webpack ou esbuild, a sintaxe pode ser transpilada e usada hoje mesmo para ambientes mais antigos.

caniuse

Conclusão

As declarações using e await using tiram do desenvolvedor a responsabilidade de lembrar manualmente de liberar cada recurso, transformando um padrão repetitivo e propenso a erros em uma garantia da própria linguagem — de forma parecida com o que outras linguagens já ofereciam há anos com seus próprios mecanismos de descarte determinístico.

Tecnologia:
js

Deixe seu Comentário!

Dúvida, sugestão ou só um "valeu!" — tudo é bem-vindo por aqui.

Comentário
Nome
Email
lock Seu e-mail não será publicado.