using

A declaration of a resource that is released automatically when the block exits, even after an error.

On this page

Example

#
TypeScript
// 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()
Result
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

#
Syntax
using resource = createResource()
await using connection = await connect()

class Resource implements Disposable {
  [Symbol.dispose]() { … }
}

More examples

#
Several resources and an exception
TypeScript
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)
}
Result
+ sheet
+ results
- results
- sheet
no answers
await using
TypeScript
// 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')
Result
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 using can’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.