Symbol.asyncDispose

Marks the async cleanup method that an await using declaration awaits at scope exit.

Signature
Symbol.asyncDispose
// as implemented on an object:
async [Symbol.asyncDispose]()

Return value

symbolThe well-known symbol used as the property key for an object's asynchronous cleanup method. The promise it returns is awaited before execution continues.

Errors

A rejection from [Symbol.asyncDispose]() means the resource was not freed; await using propagates it, wrapping any error already in flight in a SuppressedError.

Changelog

  • PHP Explicit Resource ManagementIntroduced with the await using declaration and AsyncDisposableStack. MDN lists it as limited availability — it does not yet work in some widely used browsers.

Symbol.asyncDispose is the asynchronous counterpart of Symbol.dispose. An await using declaration looks this key up on the initializer and, when the block exits, calls the method and awaits the promise it returns before moving on:

async function read() {
  await using conn = await connect();
  return conn.query("select 1");
} // await conn[Symbol.asyncDispose]() runs here

When to reach for it

Use it when releasing the resource is itself asynchronous: closing a database connection, flushing and closing a stream, committing or rolling back a transaction, awaiting an in-flight request to settle. The object is not considered disposed until that promise fulfils, so the caller cannot race ahead of an unfinished flush.

What trips people up

Support is limited and not Baseline, and await using is only valid where await is — an async function, an async generator, or a module's top level. If an object implements both symbols, await using prefers Symbol.asyncDispose while plain using requires Symbol.dispose and will not fall back. Make disposal idempotent, and remember a rejection here surfaces after the block body has already finished.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Close a database connection or transaction when an async function returns
  • Flush and close a writable stream before the caller continues
  • Await an orderly shutdown of a worker or subscription at the end of a block

Frequently asked questions

When should I use Symbol.asyncDispose instead of Symbol.dispose?

Use it whenever cleanup itself needs to await something, such as flushing a stream or closing a connection, because await using awaits the returned promise while plain using ignores it.

Where is `await using` allowed?

Anywhere await is allowed and a block scope exists: inside async functions, async generators, and at the top level of a module. It is not valid in synchronous functions.

What happens if the async dispose method rejects?

The rejection propagates out of the block after the body has completed, and if an error was already in flight it is wrapped in a SuppressedError so neither error is lost.

More examples

What await using does for you

"after" prints last because each disposal is awaited: resources unwind in reverse declaration order and execution only resumes once every cleanup promise has settled.

Example · javascript
Symbol.asyncDispose ??= Symbol("Symbol.asyncDispose");

const make = (name) => ({
  async [Symbol.asyncDispose]() {
    await new Promise((r) => setTimeout(r, 5));
    console.log("disposed", name);
  },
});

// Equivalent to: await using a = make("a"); await using b = make("b");
const a = make("a");
const b = make("b");
try {
  console.log("body");
} finally {
  await b[Symbol.asyncDispose]();
  await a[Symbol.asyncDispose]();
}
console.log("after");

Choosing between the two disposal symbols

This mirrors the lookup await using performs: it prefers Symbol.asyncDispose when present and falls back to Symbol.dispose otherwise, while a plain using declaration only ever looks for the synchronous key.

Example · javascript
Symbol.dispose ??= Symbol("Symbol.dispose");
Symbol.asyncDispose ??= Symbol("Symbol.asyncDispose");

const both = {
  [Symbol.dispose]() { console.log("sync path"); },
  async [Symbol.asyncDispose]() { console.log("async path"); },
};

const pick = (o) =>
  typeof o[Symbol.asyncDispose] === "function" ? "async" : "sync";

console.log(pick(both));
console.log(pick({ [Symbol.dispose]() {} }));
await both[Symbol.asyncDispose]();

Discussion

  • Be the first to comment on this lesson.