
using e await using: la gestione esplicita delle risorse in JavaScript
Il problema di dimenticare di chiudere una risorsa quando c'è un'eccezione anticipata, come funzionano Symbol.dispose/Symbol.asyncDispose, e un confronto pratico con try/finally.
Qualsiasi risorsa che ha bisogno di una pulizia esplicita — una connessione al database, un file handle, un lock — condivide la stessa modalità di fallimento: è facile dimenticare la chiamata di pulizia su uno dei percorsi di uscita, specialmente quando un’eccezione può saltarla del tutto. using e await using sono nuove dichiarazioni pensate apposta per rendere questo errore strutturalmente difficile da scrivere.
Il problema: pulizia che viene saltata a causa di un’eccezione
function processFile(path) {
const file = openFile(path);
const data = file.read(); // lancia un errore — parseData non gira mai, ma nemmeno file.close()
const result = parseData(data);
file.close();
return result;
}
Se file.read() lancia un errore, l’esecuzione salta fuori direttamente dalla funzione — file.close(), che sta dopo la riga fallita, non gira mai. La risorsa perde. La correzione tradizionale è try/finally:
function processFile(path) {
const file = openFile(path);
try {
const data = file.read();
return parseData(data);
} finally {
file.close();
}
}
Funziona, ma è opzionale e facile da dimenticare — niente ti obbliga ad avvolgere la risorsa in un try/finally, e con più risorse, l’annidamento diventa presto ingestibile.
using: pulizia automatica, senza bisogno di try/finally
function processFile(path) {
using file = openFile(path);
const data = file.read(); // se questo lancia un errore, file viene comunque smaltito automaticamente
return parseData(data);
}
using dichiara una variabile nello stesso modo di const, ma registra anche il valore per la pulizia automatica alla fine del blocco che la racchiude — sia che quel blocco esca normalmente, tramite return, o tramite un’eccezione lanciata. Nessun blocco finally da ricordare, nessun rischio che una chiamata di pulizia stia dopo una riga che può lanciare un errore.
Come funziona: Symbol.dispose
Perché un valore funzioni con using, ha bisogno di un metodo su Symbol.dispose — è questo che viene effettivamente chiamato all’uscita dal blocco:
function openFile(path) {
const handle = nativeOpen(path);
return {
read: () => nativeRead(handle),
[Symbol.dispose]() {
nativeClose(handle);
},
};
}
Non sei limitato a usare using su risorse fornite da librerie — qualsiasi oggetto che scrivi con un metodo [Symbol.dispose] si collega allo stesso meccanismo.
await using: l’equivalente asincrono
Alcune pulizie sono esse stesse asincrone — chiudere una connessione al database spesso richiede di fare await su un round-trip di rete. await using gestisce esattamente questo, chiamando [Symbol.asyncDispose] e aspettandolo:
async function withConnection(query) {
await using conn = await openDatabaseConnection();
return await conn.execute(query);
// conn[Symbol.asyncDispose]() viene chiamato e atteso automaticamente qui,
// sia che execute() sia riuscito, sia uscito prima, sia che abbia lanciato un errore
}
function openDatabaseConnection() {
return {
execute: (query) => runQuery(query),
async [Symbol.asyncDispose]() {
await closeConnectionGracefully();
},
};
}
Più risorse, pulite in ordine inverso
function processFiles(pathA, pathB) {
using fileA = openFile(pathA);
using fileB = openFile(pathB);
return merge(fileA.read(), fileB.read());
// fileB viene smaltito per primo, poi fileA — ordine inverso alla dichiarazione,
// la stessa convenzione che l'annidamento manuale di try/finally richiederebbe di scrivere a mano
}
Questo rispecchia esattamente come annideresti a mano i blocchi try/finally per più risorse — using lo fa semplicemente senza l’annidamento.
⚠ Non per valori senza un metodo di dispose
using/await using funzionano solo con valori che implementano Symbol.dispose/
Symbol.asyncDispose. Il normale const resta corretto per i valori ordinari — usa using
specificamente quando tieni in mano qualcosa che ha bisogno di uno smaltimento esplicito.
using vs try/finally a colpo d’occhio
try/finally |
using / await using |
|
|---|---|---|
| Richiede annidamento esplicito dei blocchi | Sì | No |
| Facile da dimenticare | Sì — niente lo impone | Più difficile — la dichiarazione stessa porta la pulizia |
| Più risorse | Blocchi annidati, verboso | Dichiarazioni piatte, pulizia automatica in ordine inverso |
| Funziona con pulizia asincrona | Sì, manualmente con await nel finally |
Sì, nativamente con await using |
FAQ
❓ Serve una libreria per usarlo già oggi?
Nessun polyfill necessario per la sintassi sui motori che la supportano — Node.js e i browser
moderni stanno progressivamente offrendo il supporto. Verifica la versione del tuo runtime target
prima di affidartici senza un transpiler (Babel/TypeScript possono compilare using a un target
più vecchio).
❓ Le classi esistenti funzionano con using senza modifiche?
Solo se implementano già Symbol.dispose o Symbol.asyncDispose, oppure le avvolgi in un piccolo
oggetto adapter che lo fa. Non è automatico per classi esistenti arbitrarie — tu (o l’autore della
libreria) dovete aggiungere il metodo esplicitamente.
❓ È simile al with di Python o allo using di C#?
Concettualmente, sì — è la stessa categoria di feature: una dichiarazione che lega la durata di vita di una risorsa a uno scope lessicale e garantisce la pulizia all’uscita. La versione di JavaScript segue la stessa idea di fondo che quei linguaggi hanno reso popolare.
Conclusione
using e await using eliminano il modo più comune in cui la pulizia delle risorse viene saltata: un’eccezione che salta oltre la riga che avrebbe dovuto chiudere le cose. Implementa Symbol.dispose/Symbol.asyncDispose su qualsiasi cosa abbia bisogno di uno smaltimento esplicito, e il linguaggio si occupa di chiamarlo — nell’ordine giusto, su ogni percorso di uscita — senza un blocco try/finally da scrivere o dimenticare.
Riferimenti

Co-Fondatore & CTO presso PAPION. Senior full-stack engineer specializzato in React, TypeScript, Node.js e sicurezza applicativa.