All Articles

TypeScript 5.2's New Keyword: 'using'

Matt Pocock
Matt PocockMatt is a well-regarded TypeScript expert known for his ability to demystify complex TypeScript concepts.

TypeScript 5.2 introduces a new keyword - using - that you can use to dispose of anything with a Symbol.dispose function when it leaves scope.

{
  const getResource = () => {
    return {
      [Symbol.dispose]: () => {
        console.log('Hooray!')
      }
    }
  }

  using resource = getResource();
} // 'Hooray!' logged to console

This is based on the TC39 proposal, which recently reached Stage 3 (out of 4) in its progress to JavaScript. This means that it's ready to be tested by early adopters.

using will be extremely useful for managing resources like file handles, database connections, and more.

Symbol.dispose

Symbol.dispose is a new global symbol in JavaScript. Anything with a function assigned to Symbol.dispose will be considered a 'resource' - "an object with a specific lifetime" - and can be used with the using keyword.

const resource = {
  [Symbol.dispose]: () => {
    console.log("Hooray!");
  },
};

await using

You can also use Symbol.asyncDispose and await using to handle resources which need to be disposed asynchronously.

const getResource = () => ({
  [Symbol.asyncDispose]: async () => {
    await someAsyncFunc();
  },
});

{
  await using resource = getResource();
}

This will await the Symbol.asyncDispose function before continuing.

This will be useful for resources such as database connections, where you want to ensure that the connection is closed before the program continues.

Use cases

File handles

Accessing the file system via file handlers in node could be a lot easier with using.

Without using:

import { open } from "node:fs/promises";

let filehandle;
try {
  filehandle = await open("thefile.txt", "r");
} finally {
  await filehandle?.close();
}

With using:

import { open } from "node:fs/promises";

const getFileHandle = async (path: string) => {
  const filehandle = await open(path, "r");

  return {
    filehandle,
    [Symbol.asyncDispose]: async () => {
      await filehandle.close();
    },
  };
};

{
  await using file = await getFileHandle("thefile.txt");

  // Do stuff with file.filehandle

} // Automatically disposed!

Database connections

Managing database connections is a common use case for using in C#.

Without using:

const connection = await getDb();

try {
  // Do stuff with connection
} finally {
  await connection.close();
}

With using:

const getConnection = async () => {
  const connection = await getDb();

  return {
    connection,
    [Symbol.asyncDispose]: async () => {
      await connection.close();
    },
  };
};

{
  await using db = await getConnection();

  // Do stuff with db.connection

} // Automatically closed!
Share on Twitter
Matt's signature

Share this article with your friends

`any` Considered Harmful, Except For These Cases

Discover when it's appropriate to use TypeScript's any type despite its risks. Learn about legitimate cases where any is necessary.

Matt Pocock
Matt Pocock

No, TypeScript Types Don't Exist At Runtime

Learn why TypeScript's types don't exist at runtime. Discover how TypeScript compiles down to JavaScript and how it differs from other strongly-typed languages.

Matt Pocock
Matt Pocock

Deriving vs Decoupling: When NOT To Be A TypeScript Wizard

In this book teaser, we discuss deriving vs decoupling your types: when building relationships between your types or segregating them makes sense.

Matt Pocock
Matt Pocock

NoInfer: TypeScript 5.4's New Utility Type

Learn how TypeScript's new utility type, NoInfer, can improve inference behavior by controlling where types are inferred in generic functions.

Matt Pocock
Matt Pocock