Hányszor írtál már ilyet: megnyitsz egy fájlt, lekérsz egy DB-kapcsolatot a poolból, feloldasz egy lockot – aztán try/finally-be csomagolod, mert ha közben dobódik egy hiba, a takarítás simán lemarad. Most, hogy két-három ilyen erőforrás egymásba ágyazva várna felszabadításra, a kód recseg-ropog. A JavaScript ezt végre natívan megoldja: a using és await using deklaráció garantálja, hogy egy erőforrás felszabadítása lefut – blokk végén, korai return-nél, vagy dobott hibánál is. Kipróbáltam Node 22-n és Node 24-en is, és a kettő között meglepően nagy a különbség.

Mi is ez pontosan
A TC39 Explicit Resource Management javaslat 2026 közepén elérte a Stage 4-et – a tc39/proposals lezárt javaslatok listája szerint várhatóan az ES2027 specifikáció része lesz. A lényege: ha egy változót using-gal deklarálsz, a hozzá tartozó [Symbol.dispose]() metódus automatikusan lefut, amikor a változó kikerül a scope-ból – blokk végén, korai visszatérésnél, vagy akár dobott kivétel esetén is. Az await using ugyanez aszinkron erőforrásokra, a [Symbol.asyncDispose]()-t hívja meg, és megvárja a Promise-t. Több using deklaráció fordított sorrendben (LIFO) dobódik el, pont úgy, mint egymásba ágyazott finally blokkoknál.
A MDN using dokumentációja jól összefoglalja a szintaktikai korlátokat is: script top-level-en és switch blokk tetején nem használható, viszont függvénytesten, modul top-leveljén és blokkokon belül igen. Nézzük meg élesben, Node.js-en (a példákat Node.js 24.19.0-n futtattam, flag nélkül – ez a fontos rész, mire ez alább kiderül).
import { open } from 'node:fs/promises';
async function withAwaitUsing() {
await using fh = await open('/tmp/test-dispose.txt', 'w');
await fh.writeFile('hello resource management\n');
console.log('write done, handle still open here');
}
await withAwaitUsing();
console.log('after function: handle already closed automatically');
A FileHandle objektumnak Node.js-ben van beépített [Symbol.asyncDispose] metódusa – ez zárja le a fájlleírót. Nem kell külön try/finally, nem kell emlékezni a close()-ra korai return vagy hiba esetén sem. A kimenet:
write done, handle still open here
after function: handle already closed automatically
Ha kettő megy félre egyszerre: SuppressedError
Az igazán jó rész a hibakezelés. Mi történik, ha a blokk törzse dob egy hibát, és a takarítás (a dispose) is elhasal? A régi try/finally-nél az egyik hiba egyszerűen felülírja, elnyeli a másikat – ez klasszikus, nehezen debuggolható forrás. A specifikáció erre bevezette a SuppressedError-t: mindkét hiba megmarad, láncolva.
class Bad {
[Symbol.dispose]() { throw new Error('dispose failed'); }
}
try {
using r = new Bad();
throw new Error('body failed');
} catch (e) {
console.log(e instanceof SuppressedError); // true
console.log(e.message); // An error was suppressed during disposal
console.log(e.error.message); // dispose failed – az új hiba
console.log(e.suppressed.message); // body failed – az eredeti, "elnyelt" hiba
}
Node 24-en pontosan ezt a kimenetet adja – semmi nem vész el, mindkét hiba elérhető marad az error/suppressed mezőkön keresztül.
DisposableStack: amikor előre nem tudod, mennyi erőforrás lesz
A using jól működik fix számú, előre ismert erőforrásra. De mi van, ha ciklusban vagy feltételesen szerzel erőforrásokat? Erre van a DisposableStack (és aszinkron párja, az AsyncDisposableStack): egy verem, amibe dinamikusan regisztrálhatsz felszabadítandó dolgokat, maga a stack pedig Symbol.dispose-olható, tehát using-gal kombinálható.
class Conn {
close() { console.log('conn closed'); }
}
function doWork() {
using stack = new DisposableStack();
stack.defer(() => console.log('c1 disposed'));
const conn = stack.adopt(new Conn(), (c) => c.close());
console.log('doing work with', conn.constructor.name);
}
doWork();
// kimenet: doing work with Conn / conn closed / c1 disposed
// – fordított sorrendben, ahogy regisztrálva voltak
defer() egy tetszőleges callback-et regisztrál, adopt() pedig egy nem-disposable objektumhoz rendel egyedi takarító függvényt (pl. egy régi API-hoz, ami csak close()-t ismer, nem Symbol.dispose-t). Ez pont az a réteg, ami meglévő, nem „dispose-aware” library-k köré is használható.
A támogatottság: itt jön a csavar
A Symbol.dispose és Symbol.asyncDispose szimbólumok már Node.js 18.18 óta léteznek, de a using szintaxis maga sokkal később, csak a V8 megfelelő verziójával került be. A dokumentáció szerint az ERM (Explicit Resource Management) Node.js 24-től alapból (flag nélkül) engedélyezett, és a Symbol.dispose/Symbol.asyncDispose a 24.2.0-s kiadásban került ki a dokumentációban is jelzett experimental státuszból. A MDN böngésző-kompatibilitási adatbázisa szerint a DisposableStack konstruktor Node.js 24.0.0-tól, Chrome/Edge 134-től és Firefox 141-től elérhető – Safariban még nincs.
És itt jön a rész, amit érdemes tudni, mielőtt bárki bevezeti: Node 22-n (jelenleg is támogatott LTS-ág) a using csak a --js-explicit-resource-management V8-flaggel érhető el, és a flag saját leírása is „in progress / experimental”. Ez nem csak formalitás – lokálisan leteszteltem Node 22.23.2-n (V8 12.4), és:
- A szinkron
usinga flaggel elfogadja a szintaxist és lefuttatja adispose-t. - Az
await usingviszont ugyanezzel a flaggel SyntaxError-t dob – async függvényen belül és modul top-levelen is –, tehát a fájlkezelős példa fent Node 22-n el sem indul. - Ha a
[Symbol.dispose]()hibát dob, az a flaggel némán elnyelődik – se kivétel, seSuppressedError, a program úgy folytatja, mintha semmi nem történt volna. Ugyanez Node 24-en unflagged helyesen szabályosError-ként, illetveSuppressedError-ként jelenik meg, ahogy fentebb látszik.
Ez elég komoly különbség ahhoz, hogy ne bízz a flag-elt Node 22-es viselkedésben semmi olyanban, ami hibakezelésre épít. Ha most akarod bevezetni, vagy Node 24+ kell hozzá, vagy várj, vagy tesztelj a saját Node-verziódon, mielőtt bármi élesbe kerül belőle.
TypeScript-ben
A TypeScript 5.2 óta ismeri a using/await using szintaxist és típusait, "target": "es2022" és a "lib" listában "esnext.disposable" mellett. A release note maga hívja fel a figyelmet arra, hogy a legtöbb futtatási környezet natívan nem támogatja még – ha régebbi célra fordítasz, saját magadnak kell polyfillt adnod Symbol.dispose, Symbol.asyncDispose, DisposableStack, AsyncDisposableStack és SuppressedError számára. A típusrendszer tehát előrébb van, mint a legtöbb futtatási cél – build- és target-konfiguráció nélkül könnyen abba a hibába lehet futni, hogy fordítási időben mindent helyesnek látsz, futásidőben pedig ReferenceError-t kapsz a régebbi Node-on vagy böngészőben.
Mikor éri meg
Ott, ahol eddig try/finally-t vagy egymásba ágyazott try blokkokat írtál: fájlleírók, DB-connectionök visszaadása a poolba, lockok feloldása, ideiglenes fájlok törlése, teszteknél a setup/teardown párosítása. A nyereség nem az, hogy kevesebbet gépelsz – inkább az, hogy a felszabadítás garantáltan lefut, és a hibák nem tűnnek el útközben. Ha viszont már van egy jól bejáratott try/finally-alapú megoldásod és nincs Node 24+ / friss böngésző célod, nincs sürgős ok az átírásra – ez inkább az új kódnál éri meg alapból így írni.