DEV Community

Cover image for Explicit Resource Management in JS: The using Keyword
Zachary Lee
Zachary Lee

Posted on • Originally published at webdeveloper.beehiiv.com on

Explicit Resource Management in JS: The using Keyword

JavaScript is soon to introduce a new keyword: using. It is currently in the third stage (of four) of the TC39 proposal process, which means we will soon be able to use it in production.

This feature is inspired by similar concepts in C# and Rust, particularly Rust's ownership system, which automatically manages resource cleanup to prevent common errors like memory leaks and use-after-free bugs. It can automatically handle resources that implement the Symbol.dispose method when they are no longer needed, significantly simplifying resource management in applications.

Understanding Symbol.dispose

In JavaScript (and by extension, TypeScript), Symbol.dispose is a new global symbol that identifies objects designed to be managed resources. These are objects that have a finite lifecycle and need explicit cleanup after use to free up system resources. By assigning a function to Symbol.dispose, you define how the object should be cleaned up.

Here’s a simple example

const resource = {
  [Symbol.dispose]: () => {
    console.log("Resource has been cleaned up!");
  },
};
Enter fullscreen mode Exit fullscreen mode

Using using for Synchronous Resource Management

The using keyword allows you to declare a resource that should be automatically disposed of once it goes out of scope. This is particularly useful for managing resources like file handles or network connections. Here's how you might use it:

{
  const getResource = () => ({
    [Symbol.dispose]: () => console.log('Resource is now disposed!')
  });

  using resource = getResource();
}
// Output: 'Resource is now disposed!'
Enter fullscreen mode Exit fullscreen mode

This block ensures that as soon as the block is exited, the resource’s dispose method is called, thereby preventing resource leakage.

Asynchronous Resource Disposal with await using

For resources that require asynchronous cleanup, JavaScript supports await using. This is useful for operations that need to perform asynchronous tasks as part of their cleanup, like closing database connections or flushing buffers to a file.

Here’s an example using await using:

const getResourceAsync = () => ({
  [Symbol.asyncDispose]: async () => {
    await someAsyncCleanupFunction();
  }
});

{
  await using resource = getResourceAsync();
}
Enter fullscreen mode Exit fullscreen mode

Practical Examples

Handling Database Connections

Database connections are a critical resource that needs careful management to avoid exhausting connection pools or holding onto unnecessary locks.

Without using:

const connection = await getDatabaseConnection();
try {
  // Query the database
} finally {
  await connection.close();
}
Enter fullscreen mode Exit fullscreen mode

With using:

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

{
  await using db = getConnection();
  // Use db.connection for queries
}
// Connection is automatically closed here
Enter fullscreen mode Exit fullscreen mode

Managing File Handles

Handling files often requires meticulous resource management to avoid leaving open file handles that can lock files or consume memory. Here’s how you might handle a file with and without using:

Without using:

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

let fileHandle;
try {
  fileHandle = await open("example.txt", "r");
  // Read or write to the file
} finally {
  if (fileHandle) {
    await fileHandle.close();
  }
}
Enter fullscreen mode Exit fullscreen mode

With using:

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

const getFileHandle = async (path) => {
  const fileHandle = await open(path, "r");
  return {
    fileHandle,
    [Symbol.asyncDispose]: async () => await fileHandle.close()
  };
};

{
  await using file = getFileHandle("example.txt");
  // Operate on file.fileHandle
}
// File is automatically closed after this block
Enter fullscreen mode Exit fullscreen mode

Conclusion

This feature not only reduces boilerplate code but also helps in preventing common programming errors related to resource management, bringing JavaScript closer to the safety and convenience seen in languages like Rust and C#.

If you find this helpful, please consider subscribing to my newsletter for more insights on web development. Thank you for reading!

Top comments (0)