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 hereImplementing
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 exitIf 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 resAIf 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().