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
- Fechar arquivos, sockets e conexões de banco de dados sem depender de lembrar um
finally. - Liberar locks e semáforos, garantindo que a liberação aconteça mesmo em caminhos de erro.
- Remover listeners e observers temporários criados dentro de uma função.
- Agrupar limpeza de múltiplos recursos relacionados com
DisposableStack, em vez de aninhar váriostry/finally.
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.
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.
0 Comentário
Deixe seu Comentário!
Dúvida, sugestão ou só um "valeu!" — tudo é bem-vindo por aqui.