JavaScript Explicit Resource Management and Using

The Explicit Resource Management proposal introduces a deterministic, scope-based mechanism for managing the lifecycle of resources in JavaScript. By introducing the using and await using declarations, alongside built-in symbols like Symbol.dispose and Symbol.asyncDispose, JavaScript now provides a native way to clean up resources like file handles, database connections, and memory allocations without relying on verbose and error-prone try...finally blocks.

The Problem with Manual Cleanup

Before this proposal, managing resources required manual intervention. Developers had to ensure that opened resources were closed explicitly, typically wrapping operations in try...finally statements:

const resource = openResource();
try {
  resource.doSomething();
} finally {
  resource.close();
}

This pattern becomes cumbersome and prone to leaks when managing multiple resources or handling complex branching logic and asynchronous operations.

The using Keyword

The using declaration operates similarly to const, but it automatically binds the lifecycle of a resource to the block scope in which it is declared. When execution exits the scope—whether by reaching the end of the block, returning, or throwing an error—the resource’s disposal method is automatically invoked.

{
  using resource = openResource();
  resource.doSomething();
} // resource[Symbol.dispose]() is automatically called here

Implementing Symbol.dispose

An object becomes a “disposable” by implementing a method keyed with the well-known symbol Symbol.dispose.

class TempFile {
  #path;
  constructor(path) {
    this.#path = path;
  }

  [Symbol.dispose]() {
    deleteFile(this.#path);
    console.log("File cleaned up.");
  }
}

{
  using file = new TempFile("/tmp/data.txt");
  // Perform file operations
} // Logs: "File cleaned up."

Asynchronous Disposables with await using

For resources that require asynchronous cleanup—such as closing network streams or committing database transactions—the proposal introduces await using paired with Symbol.asyncDispose.

class DatabaseConnection {
  async [Symbol.asyncDispose]() {
    await this.disconnect();
    console.log("Database disconnected.");
  }

  async disconnect() {
    // Teardown logic
  }
}

async function runQuery() {
  await using db = new DatabaseConnection();
  // Perform database queries
} // Automatically awaits db[Symbol.asyncDispose]() upon exit

If an object only implements Symbol.dispose, await using will fall back to calling the synchronous method.

Disposal Order and Error Handling

Resources declared with using are disposed of in reverse order of their declaration (Last-In, First-Out). This ensures that dependent resources are cleaned up safely before the resources they rely on are closed.

{
  using resA = getResourceA();
  using resB = getResourceB(); // Depends on resA
} 
// resB is disposed first, followed by resA

If an error occurs both inside the block and during the disposal process, JavaScript handles the conflict by throwing a SuppressedError. The SuppressedError contains references to both the primary exception and the suppressed error that occurred during cleanup.

DisposableStack and AsyncDisposableStack

For dynamic resource management outside fixed block scopes, the proposal provides the DisposableStack and AsyncDisposableStack classes. These container objects allow developers to push disposables or arbitrary cleanup callbacks onto a stack programmatically and dispose of them all at once via .dispose() or .disposeAsync().