using
A declaration of a resource that is released automatically when the block exits, even after an error.
Example
#// Polyfill: only needed where the built-in Symbol.dispose is missing
(Symbol as { dispose?: symbol }).dispose ??= Symbol('Symbol.dispose')
class DbConnection implements Disposable {
constructor(private name: string) {
console.log(`Opening ${name}`)
}
query(sql: string) {
console.log(`${this.name}: ${sql}`)
}
[Symbol.dispose]() {
console.log(`Closing ${this.name}`)
}
}
function saveProgress() {
using db = new DbConnection('progress-db')
db.query('UPDATE progress SET exp = exp + 20')
console.log('End of function')
}
saveProgress()Opening progress-db progress-db: UPDATE progress SET exp = exp + 20 End of function Closing progress-db
Definition and usage
#A using declaration works like const, but it also cleans up after itself. When the program leaves the block that contains such a declaration, whether it reaches the end, exits through return, or exits through an exception, the stored object’s [Symbol.dispose]() method is called. That way you close a database connection, a file, or a lock without writing try...finally by hand.
An object works with using if it has a [Symbol.dispose]() method, meaning it satisfies the Disposable interface. The values null and undefined are allowed and are skipped. Several resources in one block are released in the reverse order of creation. The await using version waits for asynchronous cleanup in the [Symbol.asyncDispose]() method and works wherever await is allowed.
TypeScript supports this syntax since version 5.2. In lib you need the esnext.disposable types (or all of esnext). With a target lower than ESNext, the compiler turns using into helper code that relies on Symbol.dispose. Node.js 22 has this symbol built in, but not every environment knows it, which is why the first example starts with a one-line polyfill.
Syntax
#using resource = createResource()
await using connection = await connect()
class Resource implements Disposable {
[Symbol.dispose]() { … }
}More examples
#function lock(name: string): Disposable {
console.log(`+ ${name}`)
return { [Symbol.dispose]: () => console.log(`- ${name}`) }
}
function gradeExam() {
using sheet = lock('sheet')
using results = lock('results')
throw new Error('no answers')
}
try {
gradeExam()
} catch (error) {
console.log((error as Error).message)
}+ sheet + results - results - sheet no answers
// await using relies on Symbol.asyncDispose, so it gets a polyfill too
(Symbol as { asyncDispose?: symbol }).asyncDispose ??= Symbol('Symbol.asyncDispose')
async function connect(name: string): Promise<AsyncDisposable> {
console.log(`Connecting to ${name}`)
return {
async [Symbol.asyncDispose]() {
console.log(`Disconnecting from ${name}`)
}
}
}
async function syncProgress() {
await using api = await connect('progress-api')
console.log('Sending progress')
}
await syncProgress()
console.log('Done')Connecting to progress-api Sending progress Disconnecting from progress-api Done
Good practices
#- The runtime error “Symbol.dispose is not defined” means the environment doesn’t know this symbol yet. Put the polyfill from the first example at the very start of your program, before the definitions of any classes that use it.
- A variable declared with
usingcan’t be reassigned. Trying to do so fails with the error “Cannot assign to 'db' because it is a constant”. - If a resource is only needed for a moment, open a separate
{ … }block for it. It will be released at the closing brace, not only at the end of the function.
Related entries
#Practise it in lessons
#Spotted a mistake or missing an example? Write to us.