using

The using declaration declares block-scoped local variables that are synchronously disposed. Like const, variables declared with using must be initialized and cannot be reassigned. The variable's value must be either null, undefined, or an object with a [Symbol.dispose]() method. When the variable goes out of scope, the [Symbol.dispose]() method of the object is called, to ensure that resources are freed.

Syntax

js
using name1 = value1;
using name1 = value1, name2 = value2;
using name1 = value1, name2 = value2, /* …, */ nameN = valueN;
nameN

The name of the variable to declare. Each must be a legal JavaScript identifier and not a destructuring binding pattern.

valueN

Initial value of the variable. It can be any legal expression but its value must be either null, undefined, or an object with a [Symbol.dispose]() method.

Description

This declaration can be used:

Most notably, it cannot be used:

  • At the top level of a script, because script scopes are persistent.
  • At the top level of a switch statement.
  • In the initializer of a for...in loop. Because the loop variable can only be a string or symbol, this doesn't make sense.

A using declares a disposable resource that's tied to the lifetime of the variable's scope (block, function, module, etc.). When the scope exits, the resource is disposed of synchronously. The variable is allowed to have value null or undefined, so the resource can be optionally present.

When the variable is first declared and its value is non-nullish, a disposer is retrieved from the object. If the [Symbol.dispose] property doesn't contain a function, a TypeError is thrown. This disposer is saved to the scope.

When the variable goes out of scope, the disposer is called. If the scope contains multiple using or await using declarations, all disposers are run in the reverse order of declaration, regardless of the type of declaration. All disposers are guaranteed to run (much like the finally block in try...catch...finally). All errors thrown during disposal, including the initial error that caused the scope exit (if applicable), are all aggregated inside one SuppressedError, with each earlier exception as the suppressed property and the later exception as the error property. This SuppressedError is thrown after disposal is complete.

using ties resource management to lexical scopes, which is both convenient and sometimes confusing. There are many ways to preserve the variable's value when the variable itself is out of scope, so you may hold a reference to an already-disposed resource. See below for some examples where it may not behave how you expect. If you want to hand-manage resource disposal, while maintaining the same error handling guarantees, you can use DisposableStack instead.

Examples

In the following examples, we assume a simple Resource class that has a getValue method and a [Symbol.dispose]() method:

js
class Resource {
  value = Math.random();
  #isDisposed = false;

  getValue() {
    if (this.#isDisposed) {
      throw new Error("Resource is disposed");
    }
    return this.value;
  }

  [Symbol.dispose]() {
    this.#isDisposed = true;
    console.log("Resource disposed");
  }
}

using in a block

The resource declared with using is disposed when exiting the block.

js
{
  using resource = new Resource();
  console.log(resource.getValue());
  // resource disposed here
}

using in a function

You can use using in a function body. In this case, the resource is disposed when the function finishes executing, immediately before the function returns.

js
function example() {
  using resource = new Resource();
  return resource.getValue();
}

Here, resource[Symbol.dispose]() will be called after getValue(), before the return statement executes.

The resource may outlive the declaration, in case it's captured by a closure:

js
function example() {
  using resource = new Resource();
  return () => resource.getValue();
}

In this case, if you call example()(), you will always execute getValue on a resource that's already disposed, because the resource was disposed when example returns. In case you want to dispose the resource immediately after the callback has been called once, consider this pattern:

js
function example() {
  const resource = new Resource();
  return () => {
    using resource2 = resource;
    return resource2.getValue();
  };
}

Here, we alias a const-declared resource to a using-declared resource, so that the resource is only disposed after the callback is called; note that if it is never called then the resource will never be cleaned up.

using in a module

You can use using at the top level of a module. In this case, the resource is disposed when the module finishes executing.

js
using resource = new Resource();
export const value = resource.getValue();
// resource disposed here

export using is invalid syntax, but you can export a variable declared elsewhere using using:

js
using resource = new Resource();
export { resource };

This is still discouraged, because the importer will always receive a disposed resource. Similar to the closure problem, this causes the value of resource to live longer than the variable.

using with for...of

You can use using in the initializer of a for...of loop. In this case, the resource is disposed on every loop iteration.

js
const resources = [new Resource(), new Resource(), new Resource()];
for (using resource of resources) {
  console.log(resource.getValue());
  // resource disposed here
}

Multiple using

The following are two equivalent ways to declare multiple disposable resources:

js
using resource1 = new Resource(),
  resource2 = new Resource();

// OR

using resource1 = new Resource();
using resource2 = new Resource();

In both cases, when the scope exits, resource2 is disposed before resource1. This is because resource2 may have a dependency on resource1, so it's disposed first to ensure that resource1 is still available when resource2 is disposed.

Optional using

using allows the variable to have value null or undefined, so the resource can be optionally present. This means you don't have to do this:

js
function acquireResource() {
  // Imagine some real-world relevant condition here,
  // such as whether there's space to allocate for this resource
  if (Math.random() < 0.5) {
    return null;
  }
  return new Resource();
}

const maybeResource = acquireResource();

if (maybeResource) {
  using resource = maybeResource;
  console.log(resource.getValue());
} else {
  console.log(undefined);
}

But can do this:

js
using resource = acquireResource();
console.log(resource?.getValue());

using declaration without using the variable

You can achieve automatic resource disposing using using, without even using the variable. This is very useful for setting up a context within a block, such as creating a lock:

js
{
  using _ = new Lock();
  // Perform concurrent operations here
  // Lock disposed (released) here
}

Note that _ is a normal identifier, but it's a convention to use it as a "throwaway" variable. To create multiple unused variables, you need to use distinct names, for example by using a variable name prefixed with _.

Initialization and temporal dead zones

using variables are subject to the same temporal dead zone restriction as let and const variables. This means that you can't access the variable before the initialization—the valid lifetime of the resource is strictly from its initialization to the end of its scope. This is enables RAII-style resource management.

js
let useResource;
{
  useResource = () => resource.getValue();
  useResource(); // Error: Cannot access 'resource' before initialization
  using resource = new Resource();
  useResource(); // Valid
}
useResource(); // Error: Resource is disposed

Error handling

The using declaration is the most useful for managing resource disposal in the presence of errors. If you are not careful, some resources may leak because the error prevents code afterwards from executing.

js
function handleResource(resource) {
  if (resource.getValue() > 0.5) {
    throw new Error("Resource value too high");
  }
}

try {
  using resource = new Resource();
  handleResource(resource);
} catch (e) {
  console.error(e);
}

This will successfully catch the error thrown by handleResource and log it, and no matter if handleResource throws an error or not, the resource is disposed before exiting the try block.

Here, if you don't use using, you may do something like:

js
try {
  const resource = new Resource();
  handleResource(resource);
  resource[Symbol.dispose]();
} catch (e) {
  console.error(e);
}

But, if handleResource() throws an error, then control never reaches resource[Symbol.dispose](), and the resource is leaked. Furthermore, if you have two resources, then errors thrown in earlier disposals may prevent later disposals from running, leading to more leaks.

Consider a more complicated case where the disposer itself throws an error:

js
class CantDisposeMe {
  #name;
  constructor(name) {
    this.#name = name;
  }
  [Symbol.dispose]() {
    throw new Error(`Can't dispose ${this.#name}`);
  }
}

let error;

try {
  using resource1 = new CantDisposeMe("resource1");
  using resource2 = new CantDisposeMe("resource2");
  throw new Error("Error in main block");
} catch (e) {
  error = e;
}

You can inspect the error thrown in your browser's console. It has the following structure:

SuppressedError: An error was suppressed during disposal
  suppressed: SuppressedError: An error was suppressed during disposal
    suppressed: Error: Can't dispose resource1
    error: Error: Error in main block
  error: Error: Can't dispose resource2

As you can see, error contains all the errors that were thrown during disposal, as a SuppressedError. Each additional error is added as the error property, and the original error is added as the suppressed property.

Specifications

No specification found

No specification data found for javascript.statements.using.
Check for problems with this page or contribute a missing spec_url to mdn/browser-compat-data. Also make sure the specification is included in w3c/browser-specs.

Browser compatibility

See also