Skip to content
Permalink

Comparing changes

This is a direct comparison between two commits made in this repository or its related repositories. View the default comparison for this range or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: tc39/proposal-explicit-resource-management
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 94a888d4ab507824d28c3e0a6806173a3254cdff
Choose a base ref
..
head repository: tc39/proposal-explicit-resource-management
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 663973d378f98a09516faaa2714ce898c10a2fb1
Choose a head ref
Showing with 1,980 additions and 391 deletions.
  1. +660 −72 README.md
  2. +1,320 −319 spec.emu
732 changes: 660 additions & 72 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
# ECMAScript Explicit Resource Management

> **NOTE:** This proposal has subsumed the [Async Explicit Resource Management](https://github.com/tc39/proposal-async-explicit-resource-management)
> proposal. This proposal repository should be used for further discussion of both sync and async of explicit resource
> management.
This proposal intends to address a common pattern in software development regarding
the lifetime and management of various resources (memory, I/O, etc.). This pattern
generally includes the allocation of a resource and the ability to explicitly
release critical resources.

For example, ECMAScript Generator Functions expose this pattern through the
For example, ECMAScript Generator Functions and Async Generator Functions expose this pattern through the
`return` method, as a means to explicitly evaluate `finally` blocks to ensure
user-defined cleanup logic is preserved:

```js
// sync generators
function * g() {
const handle = acquireFileHandle(); // critical resource
try {
@@ -30,9 +35,32 @@ finally {
}
```

```js
// async generators
async function * g() {
const handle = acquireStream(); // critical resource
try {
...
}
finally {
await stream.close(); // cleanup
}
}

const obj = g();
try {
const r = await obj.next();
...
}
finally {
await obj.return(); // calls finally blocks in `g`
}
```

As such, we propose the adoption of a novel syntax to simplify this common pattern:

```js
// sync disposal
function * g() {
using handle = acquireFileHandle(); // block-scoped critical resource
} // cleanup
@@ -43,19 +71,32 @@ function * g() {
} // calls finally blocks in `g`
```

```js
// async disposal
async function * g() {
using stream = acquireStream(); // block-scoped critical resource
...
} // cleanup

{
await using obj = g(); // block-scoped declaration
const r = await obj.next();
} // calls finally blocks in `g`
```

In addition, we propose the addition of two disposable container objects to assist
with managing multiple resources:

- `DisposableStack` — A stack-based container of disposable resources.
- ~~`AsyncDisposableStack` — A stack-based container of asynchronously disposable resources.~~
_`AsyncDisposableStack`_ has been deferred to a [follow-on proposal][async-using].
- `AsyncDisposableStack` — A stack-based container of asynchronously disposable resources.

## Status

**Stage:** 3 \
**Champion:** Ron Buckton (@rbuckton) \
**Last Presented:** November/December, 2022 ([slides](https://1drv.ms/p/s!AjgWTO11Fk-TkoJoXa_RG_DaDAaoqA?e=A1aYah),
notes TBA)
**Last Presented:** March, 2023 ([slides](https://1drv.ms/p/s!AjgWTO11Fk-Tkodu1RydtKh2ZVafxA?e=yasS3Y),
[notes #1](https://github.com/tc39/notes/blob/main/meetings/2023-03/mar-21.md#async-explicit-resource-management),
[notes #2](https://github.com/tc39/notes/blob/main/meetings/2023-03/mar-23.md#async-explicit-resource-management-again))

_For more information see the [TC39 proposal process](https://tc39.es/process-document/)._

@@ -103,6 +144,7 @@ This proposal is motivated by a number of cases:
```
- Avoiding lengthy code when managing multiple resources correctly:
```js
// sync disposal
{ // block avoids leaking `a` or `b` to outer scope
const a = ...;
try {
@@ -129,6 +171,34 @@ This proposal is motivated by a number of cases:
using a = ..., b = ...;
...
```
```js
// async sync disposal
{ // block avoids leaking `a` or `b` to outer scope
const a = ...;
try {
const b = ...;
try {
...
}
finally {
await b.close(); // ensure `b` is closed before `a` in case `b`
// depends on `a`
}
}
finally {
await a.close(); // ensure `a` is closed even if `b.close()` throws
}
}
// both `a` and `b` are out of scope
```
Compared to:
```js
// avoids leaking `a` or `b` to outer scope
// ensures `b` is disposed before `a` in case `b` depends on `a`
// ensures `a` is disposed even if disposing `b` throws
await using a = ..., b = ...;
...
```
- Non-blocking memory/IO applications:
```js
import { ReaderWriterLock } from "...";
@@ -225,7 +295,7 @@ This proposal is motivated by a number of cases:
# Definitions

- _Resource_ — An object with a specific lifetime, at the end of which either a lifetime-sensitive operation
should be performed or a non-gargbage-collected reference (such as a file handle, socket, etc.) should be closed or
should be performed or a non-garbage-collected reference (such as a file handle, socket, etc.) should be closed or
freed.
- _Resource Management_ — A process whereby "resources" are released, triggering any lifetime-sensitive operations
or freeing any related non-garbage-collected references.
@@ -249,10 +319,147 @@ using x = expr1; // resource w/ local binding
using y = expr2, z = expr4; // multiple resources
```


# Grammar

Please refer to the [specification text][Specification] for the most recent version of the grammar.

## `await using` Declarations

```js
// an asynchronously-disposed, block-scoped resource
await using x = expr1; // resource w/ local binding
await using y = expr2, z = expr4; // multiple resources
```

An `await using` declaration can appear in the following contexts:
- The top level of a _Module_ anywhere _VariableStatement_ is allowed, as long as it is not immediately nested inside
of a _CaseClause_ or _DefaultClause_.
- In the body of an async function or async generator anywhere a _VariableStatement_ is allowed, as long as it is not
immediately nested inside of a _CaseClause_ or _DefaultClause_.
- In the head of a `for-of` or `for-await-of` statement.

## `await using` in `for-of` and `for-await-of` Statements

```js
for (await using x of y) ...

for await (await using x of y) ...
```

You can use an `await using` declaration in a `for-of` or `for-await-of` statement inside of an async context to
explicitly bind each iterated value as an async disposable resource. `for-await-of` does not implicitly make a non-async
`using` declaration into an async `await using` declaration, as the `await` markers in `for-await-of` and `await using`
are explicit indicators for distinct cases: `for await` *only* indicates async iteration, while `await using` *only*
indicates async disposal. For example:

```js

// sync iteration, sync disposal
for (using x of y) ; // no implicit `await` at end of each iteration

// sync iteration, async disposal
for (await using x of y) ; // implicit `await` at end of each iteration

// async iteration, sync disposal
for await (using x of y) ; // implicit `await` at end of each iteration

// async iteration, async disposal
for await (await using x of y) ; // implicit `await` at end of each iteration
```

While there is some overlap in that the last three cases introduce some form of implicit `await` during execution, it
is intended that the presence or absence of the `await` modifier in a `using` declaration is an explicit indicator as to
whether we are expecting the iterated value to have an `@@asyncDispose` method. This distinction is in line with the
behavior of `for-of` and `for-await-of`:

```js
const iter = { [Symbol.iterator]() { return [].values(); } };
const asyncIter = { [Symbol.asyncIterator]() { return [].values(); } };

for (const x of iter) ; // ok: `iter` has @@iterator
for (const x of asyncIter) ; // throws: `asyncIter` does not have @@iterator

for await (const x of iter) ; // ok: `iter` has @@iterator (fallback)
for await (const x of asyncIter) ; // ok: `asyncIter` has @@asyncIterator

```

`using` and `await using` have the same distinction:

```js
const res = { [Symbol.dispose]() {} };
const asyncRes = { [Symbol.asyncDispose]() {} };

using x = res; // ok: `res` has @@dispose
using x = asyncRes; // throws: `asyncRes` does not have @@dispose

await using x = res; // ok: `res` has @@dispose (fallback)
await using x = asyncres; // ok: `asyncRes` has @@asyncDispose
```

This results in a matrix of behaviors based on the presence of each `await` marker:

```js
const res = { [Symbol.dispose]() {} };
const asyncRes = { [Symbol.asyncDispose]() {} };
const iter = { [Symbol.iterator]() { return [res, asyncRes].values(); } };
const asyncIter = { [Symbol.asyncIterator]() { return [res, asyncRes].values(); } };

for (using x of iter) ;
// sync iteration, sync disposal
// - `iter` has @@iterator: ok
// - `res` has @@dispose: ok
// - `asyncRes` does not have @@dispose: *error*

for (using x of asyncIter) ;
// sync iteration, sync disposal
// - `asyncIter` does not have @@iterator: *error*

for (await using x of iter) ;
// sync iteration, async disposal
// - `iter` has @@iterator: ok
// - `res` has @@dispose (fallback): ok
// - `asyncRes` has @@asyncDispose: ok

for (await using x of asyncIter) ;
// sync iteration, async disposal
// - `asyncIter` does not have @@iterator: error

for await (using x of iter) ;
// async iteration, sync disposal
// - `iter` has @@iterator (fallback): ok
// - `res` has @@dispose: ok
// - `asyncRes` does not have @@dispose: error

for await (using x of asyncIter) ;
// async iteration, sync disposal
// - `asyncIter` has @@asyncIterator: ok
// - `res` has @@dispose: ok
// - `asyncRes` does not have @@dispose: error

for await (await using x of iter) ;
// async iteration, async disposal
// - `iter` has @@iterator (fallback): ok
// - `res` has @@dispose (fallback): ok
// - `asyncRes` does has @@asyncDispose: ok

for await (await using x of asyncIter) ;
// async iteration, async disposal
// - `asyncIter` has @@asyncIterator: ok
// - `res` has @@dispose (fallback): ok
// - `asyncRes` does has @@asyncDispose: ok
```

Or, in table form:

| Syntax | Iteration | Disposal |
|:---------------------------------|:------------------------------:|:----------------------------:|
| `for (using x of y)` | `@@iterator` | `@@dispose` |
| `for (await using x of y)` | `@@iterator` | `@@asyncDispose`/`@@dispose` |
| `for await (using x of y)` | `@@asyncIterator`/`@@iterator` | `@@dispose` |
| `for await (await using x of y)` | `@@asyncIterator`/`@@iterator` | `@@asyncDispose`/`@@dispose` |

# Semantics

## `using` Declarations
@@ -453,6 +660,249 @@ This will not dispose resources that are not iterated, such as if iteration is t
`using` declarations _may not_ be used in in the head of a `for-in` loop.
## `await using` Declarations
### `await using` Declarations with Explicit Local Bindings
```grammarkdown
UsingDeclaration :
`await` `using` BindingList `;`

LexicalBinding :
BindingIdentifier Initializer
```

When an `await using` declaration is parsed with _BindingIdentifier_ _Initializer_, the bindings created in the
declaration are tracked for disposal at the end of the containing async function body, _Block_, or _Module_:

```js
{
... // (1)
await using x = expr1;
... // (2)
}
```

The above example has similar runtime semantics as the following transposed representation:

```js
{
const $$try = { stack: [], error: undefined, hasError: false };
try {
... // (1)

const x = expr1;
if (x !== null && x !== undefined) {
let $$dispose = x[Symbol.asyncDispose];
if (typeof $$dispose !== "function") {
$$dispose = x[Symbol.dispose];
}
if (typeof $$dispose !== "function") {
throw new TypeError();
}
$$try.stack.push({ value: x, dispose: $$dispose });
}

... // (2)
}
catch ($$error) {
$$try.error = $$error;
$$try.hasError = true;
}
finally {
while ($$try.stack.length) {
const { value: $$expr, dispose: $$dispose } = $$try.stack.pop();
try {
await $$dispose.call($$expr);
}
catch ($$error) {
$$try.error = $$try.hasError ? new SuppressedError($$error, $$try.error) : $$error;
$$try.hasError = true;
}
}
if ($$try.hasError) {
throw $$try.error;
}
}
}
```

If exceptions are thrown both in the statements following the `await using` declaration and in the call to
`[Symbol.asyncDispose]()`, all exceptions are reported.

### `await using` Declarations with Multiple Resources

An `await using` declaration can mix multiple explicit bindings in the same declaration:

```js
{
...
await using x = expr1, y = expr2;
...
}
```

These bindings are again used to perform resource disposal when the _Block_ or _Module_ exits, however in this case each
resource's `[Symbol.asyncDispose]()` is invoked in the reverse order of their declaration. This is _approximately_
equivalent to the following:

```js
{
... // (1)
await using x = expr1;
await using y = expr2;
... // (2)
}
```

Both of the above cases would have similar runtime semantics as the following transposed representation:

```js
{
const $$try = { stack: [], error: undefined, hasError: false };
try {
... // (1)

const x = expr1;
if (x !== null && x !== undefined) {
let $$dispose = x[Symbol.asyncDispose];
if (typeof $$dispose !== "function") {
$$dispose = x[Symbol.dispose];
}
if (typeof $$dispose !== "function") {
throw new TypeError();
}
$$try.stack.push({ value: x, dispose: $$dispose });
}

const y = expr2;
if (y !== null && y !== undefined) {
let $$dispose = y[Symbol.asyncDispose];
if (typeof $$dispose !== "function") {
$$dispose = y[Symbol.dispose];
}
if (typeof $$dispose !== "function") {
throw new TypeError();
}
$$try.stack.push({ value: y, dispose: $$dispose });
}

... // (2)
}
catch ($$error) {
$$try.error = $$error;
$$try.hasError = true;
}
finally {
while ($$try.stack.length) {
const { value: $$expr, dispose: $$dispose } = $$try.stack.pop();
try {
await $$dispose.call($$expr);
}
catch ($$error) {
$$try.error = $$try.hasError ? new SuppressedError($$error, $$try.error) : $$error;
$$try.hasError = true;
}
}
if ($$try.hasError) {
throw $$try.error;
}
}
}
```

Since we must always ensure that we properly release resources, we must ensure that any abrupt completion that might
occur during binding initialization results in evaluation of the cleanup step. When there are multiple declarations in
the list, we track each resource in the order they are declared. As a result, we must release these resources in reverse
order.

### `await using` Declarations and `null` or `undefined` Values

This proposal has opted to ignore `null` and `undefined` values provided to `await using` declarations. This is
consistent with the proposed behavior for the `using` declarations in this proposal. Like in the sync case, this allows
simplifying a common case where a resource might be optional, without requiring duplication of work or needless
allocations:

```js
if (isResourceAvailable()) {
await using resource = getResource();
... // (1)
resource.doSomething()
... // (2)
}
else {
// duplicate code path above
... // (1) above
... // (2) above
}
```

Compared to:

```js
await using resource = isResourceAvailable() ? getResource() : undefined;
... // (1) do some work with or without resource
resource?.doSomething();
... // (2) do some other work with or without resource
```
### `await using` Declarations and Values Without `[Symbol.asyncDispose]` or `[Symbol.dispose]`
If a resource does not have a callable `[Symbol.asyncDispose]` or `[Symbol.asyncDispose]` member, a `TypeError` would be thrown **immediately** when the resource is tracked.
### `await using` Declarations in `for-of` and `for-await-of` Loops
An `await using` declaration _may_ occur in the _ForDeclaration_ of a `for-await-of` loop:
```js
for await (await using x of iterateResources()) {
// use x
}
```
In this case, the value bound to `x` in each iteration will be _asynchronously_ disposed at the end of each iteration.
This will not dispose resources that are not iterated, such as if iteration is terminated early due to `return`,
`break`, or `throw`.
`await using` declarations _may not_ be used in in the head of a `for-of` or `for-in` loop.
### Implicit Async Interleaving Points ("implicit `await`")
The `await using` syntax introduces an implicit async interleaving point (i.e., an implicit `await`) whenever control
flow exits an async function body, _Block_, or _Module_ containing an `await using` declaration. This means that two
statements that currently execute in the same microtask, such as:
```js
async function f() {
{
a();
} // exit block
b(); // same microtask as call to `a()`
}
```
will instead execute in different microtasks if an `await using` declaration is introduced:
```js
async function f() {
{
await using x = ...;
a();
} // exit block, implicit `await`
b(); // different microtask from call to `a()`.
}
```
It is important that such an implicit interleaving point be adequately indicated within the syntax. We believe that
the presence of `await using` within such a block is an adequate indicator, since it should be fairly easy to recognize
a _Block_ containing an `await using` statement in well-formatted code.
It is also feasible for editors to use features such as syntax highlighting, editor decorations, and inlay hints to
further highlight such transitions, without needing to specify additional syntax.
Further discussion around the `await using` syntax and how it pertains to implicit async interleaving points can be
found in [#1](https://github.com/tc39/proposal-async-explicit-resource-management/issues/1).
# Examples
The following show examples of using this proposal with various APIs, assuming those APIs adopted this proposal.
@@ -476,6 +926,14 @@ The following show examples of using this proposal with various APIs, assuming t
} // 'f2' is disposed, then 'f1' is disposed
```
### NodeJS Streams
```js
{
await using writable = ...;
writable.write(...);
} // 'writable.end()' is called and its result is awaited
```
### Logging and tracing
```js
// audit privileged function call entry and exit
@@ -496,6 +954,19 @@ export async function tryUpdate(record) {
} // synchronously release semaphore and notify the next participant
```
### Three-Phase Commit Transactions
```js
// roll back transaction if either action fails
async function transfer(account1, account2) {
await using tx = transactionManager.startTransaction(account1, account2);
await account1.debit(amount);
await account2.credit(amount);

// mark transaction success if we reach this point
tx.succeeded = true;
} // await transaction commit or rollback
```
### Shared Structs
**main_thread.js**
```js
@@ -587,28 +1058,23 @@ const { mut, cv } = data;
## Additions to `Symbol`
This proposal adds the `dispose` property to the `Symbol` constructor, whose value is the `@@dispose` internal symbol:
This proposal adds the `dispose` and `asyncDispose` properties to the `Symbol` constructor, whose values are the
`@@dispose` and `@@asyncDispose` internal symbols:
**Well-known Symbols**
| Specification Name | \[\[Description]] | Value and Purpose |
|:-|:-|:-|
| _@@dispose_ | *"Symbol.dispose"* | A method that explicitly disposes of resources held by the object. Called by the semantics of `using` declarations and by `DisposableStack` objects. |
| _@@asyncDispose_ | *"Symbol.asyncDispose"* | A method that asynchronosly explicitly disposes of resources held by the object. Called by the semantics of `await using` declarations and by `AsyncDisposableStack` objects. |
**TypeScript Definition**
```ts
interface SymbolConstructor {
readonly asyncDispose: unique symbol;
readonly dispose: unique symbol;
}
```
~~Even though this proposal no longer includes [novel syntax for async disposal](#out-of-scopedeferred), we still define
`Symbol.asyncDispose`. Async resource management is extremely valuable even without novel syntax, and
`Symbol.asyncDispose` is still necessary to support the semantics of `AsyncDisposableStack`. It is our hope that
a follow-on proposal for novel syntax will be adopted by the committee at a future date.~~
> **NOTE: `Symbol.asyncDispose` has been moved to a [follow-on proposal][async-using], per consensus in the
> [November/December, 2022 plenary](#conditional-advancement).**
## The `SuppressedError` Error
If an exception occurs during resource disposal, it is possible that it might suppress an existing exception thrown
@@ -677,12 +1143,22 @@ We also propose to add `Symbol.dispose` to the built-in `%IteratorPrototype%` as
}
```
### `%AsyncIteratorPrototype%.@@asyncDispose()`
We propose to add `Symbol.asyncDispose` to the built-in `%AsyncIteratorPrototype%` as if it had the following behavior:
```js
%AsyncIteratorPrototype%[Symbol.asyncDispose] = async function () {
await this.return();
}
```
### Other Possibilities
We could also consider adding `Symbol.dispose` to such objects as the return value from `Proxy.revocable()`, but that
is currently out of scope for the current proposal.
## The Common `Disposable` ~~and `AsyncDisposable`~~ Interface
## The Common `Disposable` and `AsyncDisposable` Interfaces
### The `Disposable` Interface
@@ -702,14 +1178,27 @@ interface Disposable {
}
```
### ~~The `AsyncDisposable` Interface~~
### The `AsyncDisposable` Interface
> **NOTE: The `AsyncDisposable` interface has been moved to a [follow-on proposal][async-using], per consensus in the
> [November/December, 2022 plenary](#conditional-advancement).**
An object is _async disposable_ if it conforms to the following interface:
## `DisposableStack` ~~and `AsyncDisposableStack`~~ container object
| Property | Value | Requirements |
|:-|:-|:-|
| `@@asyncDispose` | An async function that performs explicit cleanup. | The function should return a `Promise`. |
This proposal adds a global object that can act as a container to aggregate disposables, guaranteeing that every
**TypeScript Definition**
```ts
interface AsyncDisposable {
/**
* Disposes of resources within this object.
*/
[Symbol.asyncDispose](): Promise<void>;
}
```
## The `DisposableStack` and `AsyncDisposableStack` container objects
This proposal adds two global objects that can act as containers to aggregate disposables, guaranteeing that every
disposable resource in the container is disposed when the respective disposal method is called. If any disposable in the
container throws an error during dispose, it would be thrown at the end (possibly wrapped in a `SuppressedError` if
multiple errors were thrown):
@@ -769,21 +1258,25 @@ class DisposableStack {
}
```
`AsyncDisposableStack` is the async version of `DisposableStack` and is a container used to aggregate async disposables,
guaranteeing that every disposable resource in the container is disposed when the respective disposal method is called.
If any disposable in the container throws an error during dispose, or results in a rejected `Promise`, it would be
thrown at the end (possibly wrapped in a `SuppressedError` if multiple errors were thrown):
These classes provided the following capabilities:
- Aggregation
- Interoperation and customization
- Assist in complex construction
> **NOTE: `DisposableStack` is inspired by Python's
> [`ExitStack`](https://docs.python.org/3/library/contextlib.html#contextlib.ExitStack).**
> **NOTE: `AsyncDisposableStack` has been moved to a [follow-on proposal][async-using], per consensus in the
> [November/December, 2022 plenary](#conditional-advancement).**
> **NOTE:** `DisposableStack` is inspired by Python's
> [`ExitStack`](https://docs.python.org/3/library/contextlib.html#contextlib.ExitStack).
> **NOTE:** `AsyncDisposableStack` is inspired by Python's
> [`AsyncExitStack`](https://docs.python.org/3/library/contextlib.html#contextlib.AsyncExitStack).
### Aggregation
The `DisposableStack` ~~and `AsyncDisposableStack`~~ class provides the ability to aggregate multiple disposable resources
The `DisposableStack` and `AsyncDisposableStack` classes provid the ability to aggregate multiple disposable resources
into a single container. When the `DisposableStack` container is disposed, each object in the container is also
guaranteed to be disposed (barring early termination of the program). If any resource throws an error during dispose,
it will be collected and rethrown after all resources are disposed. If there were multiple errors, they will be wrapped
@@ -792,13 +1285,23 @@ in nested `SuppressedError` objects.
For example:
```js
// sync
const stack = new DisposableStack();
const resource1 = stack.use(getResource1());
const resource2 = stack.use(getResource2());
const resource3 = stack.use(getResource3());
stack[Symbol.dispose](); // disposes of resource3, then resource2, then resource1
```
```js
// async
const stack = new AsyncDisposableStack();
const resource1 = stack.use(getResource1());
const resource2 = stack.use(getResource2());
const resource3 = stack.use(getResource3());
await stack[Symbol.asyncDispose](); // dispose and await disposal result of resource3, then resource2, then resource1
```
If all of `resource1`, `resource2` and `resource3` were to throw during disposal, this would produce an exception
similar to the following:
@@ -814,13 +1317,13 @@ new SuppressedError(
### Interoperation and Customization
The `DisposableStack` ~~and `AsyncDisposableStack`~~ class also provides the ability to create a disposable resource from a
The `DisposableStack` and `AsyncDisposableStack` classes also provid the ability to create a disposable resource from a
simple callback. This callback will be executed when the stack's disposal method is executed.
The ability to create a disposable resource from a callback has several benefits:
- It allows developers to leverage `using` while working with existing resources that do not conform to the
`Symbol.dispose` mechanic:
- It allows developers to leverage `using`/`await using` while working with existing resources that do not conform to the
`Symbol.dispose`/`Symbol.asyncDispose` mechanic:
```js
{
using stack = new DisposableStack();
@@ -843,10 +1346,11 @@ The ability to create a disposable resource from a callback has several benefits
A user-defined disposable class might need to allocate and track multiple nested resources that should be disposed when
the class instance is disposed. However, properly managing the lifetime of these nested resources in the class
constructor can sometimes be difficult. The `move` method of `DisposableStack` ~~/`AsyncDisposableStack`~~ helps to more
constructor can sometimes be difficult. The `move` method of `DisposableStack`/`AsyncDisposableStack` helps to more
easily manage lifetime in these scenarios:
```js
// sync
class PluginHost {
#disposed = false;
#disposables;
@@ -902,6 +1406,75 @@ class PluginHost {
}
```
```js
// async
const privateConstructorSentinel = {};

class AsyncPluginHost {
#disposed = false;
#disposables;
#channel;
#socket;

/** @private */
constructor(arg) {
if (arg !== privateConstructorSentinel) throw new TypeError("Use AsyncPluginHost.create() instead");
}

// NOTE: there's no such thing as an async constructor
static async create() {
const host = new AsyncPluginHost(privateConstructorSentinel);

// Create an AsyncDisposableStack that is disposed when the constructor exits.
// If construction succeeds, we move everything out of `stack` and into
// `#disposables` to be disposed later.
await using stack = new AsyncDisposableStack();


// Create an IPC adapter around process.send/process.on("message").
// When disposed, it unsubscribes from process.on("message").
host.#channel = stack.use(new NodeProcessIpcChannelAdapter(process));

// Create a pseudo-websocket that sends and receives messages over
// a NodeJS IPC channel.
host.#socket = stack.use(new NodePluginHostIpcSocket(host.#channel));

// If we made it here, then there were no errors during construction and
// we can safely move the disposables out of `stack` and into `#disposables`.
host.#disposables = stack.move();

// If construction failed, then `stack` would be asynchronously disposed before reaching
// the line above. Event handlers would be removed, allowing `#channel` and
// `#socket` to be GC'd.
return host;
}

loadPlugin(file) {
// A disposable should try to ensure access is consistent with its "disposed" state, though this isn't strictly
// necessary since some disposables could be reusable (i.e., a Connection with an `open()` method, etc.).
if (this.#disposed) throw new ReferenceError("Object is disposed.");
// ...
}

async [Symbol.asyncDispose]() {
if (!this.#disposed) {
this.#disposed = true;
const disposables = this.#disposables;

// NOTE: we can free `#socket` and `#channel` here since they will be disposed by the call to
// `disposables[Symbol.asyncDispose]()`, below. This isn't strictly a requirement for every disposable, but is
// good housekeeping since these objects will no longer be useable.
this.#socket = undefined;
this.#channel = undefined;
this.#disposables = undefined;

// Dispose all resources in `disposables`
await disposables[Symbol.asyncDispose]();
}
}
}
```
### Subclassing `Disposable` Classes
You can also use a `DisposableStack` to assist with disposal in a subclass constructor whose superclass is disposable:
@@ -1103,13 +1676,13 @@ However there are a number drawbacks to using `for..of` as an alternative:
# Relation to DOM APIs
This proposal does not necessarily require immediate support in the HTML DOM specification, as existing APIs can still
be adapted by using `DisposableStack`. However, there are a number of APIs that could benefit from this proposal and
should be considered by the relevant standards bodies. The following is by no means a complete list, and primarily
offers suggestions for consideration. The actual implementation is at the discretion of the relevant standards bodies.
> **NOTE: A summary of DOM APIs relevant to async disposal can be found in the
> [Async Explicit Resource Management][async-using] proposal.**
be adapted by using `DisposableStack` or `AsyncDisposableStack`. However, there are a number of APIs that could benefit
from this proposal and should be considered by the relevant standards bodies. The following is by no means a complete
list, and primarily offers suggestions for consideration. The actual implementation is at the discretion of the relevant
standards bodies.
- `AudioContext` &mdash; `@@asyncDispose()` as an alias or [wrapper][] for `close()`.
- NOTE: `close()` here is asynchronous, but uses the same name as similar synchronous methods on other objects.
- `BroadcastChannel` &mdash; `@@dispose()` as an alias or [wrapper][] for `close()`.
- `EventSource` &mdash; `@@dispose()` as an alias or [wrapper][] for `close()`.
- `FileReader` &mdash; `@@dispose()` as an alias or [wrapper][] for `abort()`.
@@ -1125,21 +1698,31 @@ offers suggestions for consideration. The actual implementation is at the discre
```
- `ImageBitmap` &mdash; `@@dispose()` as an alias or [wrapper][] for `close()`.
- `IntersectionObserver` &mdash; `@@dispose()` as an alias or [wrapper][] for `disconnect()`.
- `MediaKeySession` &mdash; `@@asyncDispose()` as an alias or [wrapper][] for `close()`.
- NOTE: `close()` here is asynchronous, but uses the same name as similar synchronous methods on other objects.
- `MessagePort` &mdash; `@@dispose()` as an alias or [wrapper][] for `close()`.
- `MutationObserver` &mdash; `@@dispose()` as an alias or [wrapper][] for `disconnect()`.
- `PaymentRequest` &mdash; `@@asyncDispose()` could invoke `abort()` if the payment is still in the active state.
- NOTE: `abort()` here is asynchronous, but uses the same name as similar synchronous methods on other objects.
- `PerformanceObserver` &mdash; `@@dispose()` as an alias or [wrapper][] for `disconnect()`.
- `PushSubscription` &mdash; `@@asyncDispose()` as an alias or [wrapper][] for `unsubscribe()`.
- `ReadableStream` &mdash; `@@asyncDispose()` as an alias or [wrapper][] for `cancel()`.
- `ReadableStreamDefaultReader` &mdash; Either `@@dispose()` as an alias or [wrapper][] for `releaseLock()`, or
`@@asyncDispose()` as a [wrapper][] for `cancel()` (but probably not both).
- `RTCPeerConnection` &mdash; `@@dispose()` as an alias or [wrapper][] for `close()`.
- `RTCRtpTransceiver` &mdash; `@@dispose()` as an alias or [wrapper][] for `stop()`.
- `ReadableStreamDefaultController` &mdash; `@@dispose()` as an alias or [wrapper][] for `close()`.
- `ReadableStreamDefaultReader` &mdash; Either `@@dispose()` as an alias or [wrapper][] for `releaseLock()`, or
- `ResizeObserver` &mdash; `@@dispose()` as an alias or [wrapper][] for `disconnect()`.
- `ServiceWorkerRegistration` &mdash; `@@asyncDispose()` as a [wrapper][] for `unregister()`.
- `SourceBuffer` &mdash; `@@dispose()` as a [wrapper][] for `abort()`.
- `TransformStreamDefaultController` &mdash; `@@dispose()` as an alias or [wrapper][] for `terminate()`.
- `WebSocket` &mdash; `@@dispose()` as a [wrapper][] for `close()`.
- `Worker` &mdash; `@@dispose()` as an alias or [wrapper][] for `terminate()`.
- `WritableStream` &mdash; `@@asyncDispose()` as an alias or [wrapper][] for `close()`.
- NOTE: `close()` here is asynchronous, but uses the same name as similar synchronous methods on other objects.
- `WritableStreamDefaultWriter` &mdash; Either `@@dispose()` as an alias or [wrapper][] for `releaseLock()`, or
`@@asyncDispose()` as a [wrapper][] for `close()` (but probably not both).
- `XMLHttpRequest` &mdash; `@@dispose()` as an alias or [wrapper][] for `abort()`.
In addition, several new APIs could be considered that leverage this functionality:
@@ -1173,12 +1756,9 @@ more than once).
# Relation to NodeJS APIs
This proposal does not necessarily require immediate support in NodeJS, as existing APIs can still be adapted by using
`DisposableStack`. However, there are a number of APIs that could benefit from this proposal and should be considered by
the NodeJS maintainers. The following is by no means a complete list, and primarily offers suggestions for
consideration. The actual implementation is at the discretion of the NodeJS maintainers.
> **NOTE: A summary of NodeJS APIs relevant to async disposal can be found in the
> [Async Explicit Resource Management][async-using] proposal.**
`DisposableStack` or `AsyncDisposableStack`. However, there are a number of APIs that could benefit from this proposal
and should be considered by the NodeJS maintainers. The following is by no means a complete list, and primarily offers
suggestions for consideration. The actual implementation is at the discretion of the NodeJS maintainers.
- Anything with `ref()` and `unref()` methods &mdash; A new method or API that produces a [single-use disposer][] for
`ref()` and `unref()`.
@@ -1193,39 +1773,31 @@ consideration. The actual implementation is at the discretion of the NodeJS main
- `dns.Resolver`, `dnsPromises.Resolver` &mdash; `@@dispose()` as an alias or [wrapper][] for `cancel()`.
- `domain.Domain` &mdash; A new method or API that produces a [single-use disposer][] for `enter()` and `exit()`.
- `events.EventEmitter` &mdash; A new method or API that produces a [single-use disposer][] for `on()` and `off()`.
- `fs.promises.FileHandle` &mdash; `@@asyncDispose()` as an alias or [wrapper][] for `close()`.
- `fs.Dir` &mdash; `@@asyncDispose()` as an alias or [wrapper][] for `close()`, `@@dispose()` as an alias or [wrapper][]
for `closeSync()`.
- `fs.FSWatcher` &mdash; `@@dispose()` as an alias or [wrapper][] for `close()`.
- `http.Agent` &mdash; `@@dispose()` as an alias or [wrapper][] for `destroy()`.
- `http.ClientRequest` &mdash; Either `@@dispose()` or `@@asyncDispose()` (see
[Async Explicit Resource Management][async-using]) as an alias or [wrapper][] for `destroy()`.
- `http.IncomingMessage` &mdash; Either `@@dispose()` or `@@asyncDispose()` (see
[Async Explicit Resource Management][async-using]) as an alias or [wrapper][] for `destroy()`.
- `http.OutgoingMessage` &mdash; Either `@@dispose()` or `@@asyncDispose()` (see
[Async Explicit Resource Management][async-using]) as an alias or [wrapper][] for `destroy()`.
- `http2.Http2ServerRequest` &mdash; Either `@@dispose()` or `@@asyncDispose()` (see
[Async Explicit Resource Management][async-using]) as an alias or [wrapper][] for
- `http.ClientRequest` &mdash; Either `@@dispose()` or `@@asyncDispose()` as an alias or [wrapper][] for `destroy()`.
- `http.Server` &mdash; `@@asyncDispose()` as a [callback-adapting wrapper][] for `close()`.
- `http.ServerResponse` &mdash; `@@asyncDispose()` as a [callback-adapting wrapper][] for `end()`.
- `http.IncomingMessage` &mdash; Either `@@dispose()` or `@@asyncDispose()` as an alias or [wrapper][] for `destroy()`.
- `http.OutgoingMessage` &mdash; Either `@@dispose()` or `@@asyncDispose()` as an alias or [wrapper][] for `destroy()`.
- `http2.Http2Session` &mdash; `@@asyncDispose()` as a [callback-adapting wrapper][] for `close()`.
- `http2.Http2Stream` &mdash; `@@asyncDispose()` as a [callback-adapting wrapper][] for `close()`.
- `http2.Http2Server` &mdash; `@@asyncDispose()` as a [callback-adapting wrapper][] for `close()`.
- `http2.Http2SecureServer` &mdash; `@@asyncDispose()` as a [callback-adapting wrapper][] for `close()`.
- `http2.Http2ServerRequest` &mdash; Either `@@dispose()` or `@@asyncDispose()` as an alias or [wrapper][] for
`destroy()`.
- `http2.Http2ServerResponse` &mdash; `@@asyncDispose()` as a [callback-adapting wrapper][] for `end()`.
- `https.Server` &mdash; `@@asyncDispose()` as a [callback-adapting wrapper][] for `close()`.
- `inspector` &mdash; A new API that produces a [single-use disposer][] for `open()` and `close()`.
- `stream.Writable` &mdash; Either `@@dispose()` or `@@asyncDispose()` (see
[Async Explicit Resource Management][async-using]) as an alias or [wrapper][] for `destroy()` or
- `stream.Writable` &mdash; Either `@@dispose()` or `@@asyncDispose()` as an alias or [wrapper][] for `destroy()` or
`@@asyncDispose` only as a [callback-adapting wrapper][] for `end()` (depending on whether the disposal behavior
should be to drop immediately or to flush any pending writes).
- `stream.Readable` &mdash; Either `@@dispose()` or `@@asyncDispose()` (see
[Async Explicit Resource Management][async-using]) as an alias or [wrapper][] for `destroy()`.
- `stream.Readable` &mdash; Either `@@dispose()` or `@@asyncDispose()` as an alias or [wrapper][] for `destroy()`.
- ... and many others in `net`, `readline`, `tls`, `udp`, and `worker_threads`.
# Out-of-Scope/Deferred
Several pieces of functionality related to this proposal are currently out of scope. However, we still feel they are
important characteristics for the ECMAScript to employ in the future and may be considered for follow-on proposals:
- RAII-style [`async using` declarations][async-using] (i.e., `async using id = expr`) for async
disposables &mdash; _Postponed to [Follow-on Proposal][async-using]_
- `Symbol.asyncDispose` &mdash; _Postponed to [Follow-on Proposal][async-using]_
- `AsyncDisposableStack` &mdash; _Postponed to [Follow-on Proposal][async-using]_
- Bindingless [`using void`](./future/using-void-declaration.md) declarations (i.e., `using void = expr`) &mdash; _Postponed to Follow-on Proposal_
- Block-style [`using` statements](./future/using-statement.md) (i.e., `using (x = expr) {}`) for sync disposables &mdash; - _Withdrawn_.
- Block-style [`using await` statements](./future/using-await-statement.md) (i.e., `using await (x = expr) {}`) for async disposables - _Withdrawn_.
# Meeting Notes
* [TC39 July 24th, 2018](https://github.com/tc39/notes/blob/main/meetings/2018-07/july-24.md#explicit-resource-management)
@@ -1244,13 +1816,30 @@ important characteristics for the ECMAScript to employ in the future and may be
- Status Update only
- WH Continuing to review
- SYG (@syg) added as reviewer
* <a name="conditional-advancement"></a>TC39 December 1st, 2022 (notes TBA)
- Conclusion
* [TC39 December 1st, 2022](https://github.com/tc39/notes/blob/main/meetings/2022-11/dec-01.md#explicit-resource-management-for-stage-3) <a name="conditional-advancement"></a>
- [Conclusion](https://github.com/tc39/notes/blob/main/meetings/2022-11/dec-01.md#conclusionresolution-5)
- `using` declarations, `Symbol.dispose`, and `DisposableStack` advanced to Stage 3, under the following conditions:
- Resolution of [#103 - Argument order for `adopt()`](https://github.com/tc39/proposal-explicit-resource-management/issues/130)
- Deferral of `async using` declarations, `Symbol.asyncDispose`, and `AsyncDisposableStack`.
- `async using` declarations, `Symbol.asyncDispose`, and `AsyncDisposableStack` remain at Stage 2 as an independent
- async `using` declarations, `Symbol.asyncDispose`, and `AsyncDisposableStack` remain at Stage 2 as an independent
proposal.
* [TC39 January 31st, 2023](https://github.com/tc39/notes/blob/main/meetings/2023-01/jan-31.md#explicit-resource-management-stage-3-update)
- [Conclusion](https://github.com/tc39/notes/blob/main/meetings/2023-01/jan-31.md#conclusionresolution-3)
- Ban `await` as identifier in `using` (#138) was accepted
- Support `using` at top level of `eval` (#136) was rejected
- May consider a needs-consensus PR in the future based on implementer/community feedback.
* [TC39 February 1st, 2023](https://github.com/tc39/notes/blob/main/meetings/2023-01/feb-01.md#async-explicit-resource-management)
- [Conclusion](https://github.com/tc39/notes/blob/main/meetings/2023-01/feb-01.md#conclusionresolution-5)
- Rename `Symbol.asyncDispose` to `Symbol.disposeAsync` was rejected
- Conditional advancement to Stage 3 at March 2023 plenary pending outcome of investigation into `async using` vs.
`using await` syntax.
* [TC39 March 21st, 2023](https://github.com/tc39/notes/blob/main/meetings/2023-03/mar-21.md#async-explicit-resource-management)
- [Conclusion](https://github.com/tc39/notes/blob/main/meetings/2023-03/mar-21.md#conclusion-8)
- Committee resolves to adopt `await using` pending investigation of potential cover grammar.
* [TC39 March 23rd, 2023](https://github.com/tc39/notes/blob/main/meetings/2023-03/mar-23.md#async-explicit-resource-management-again)
- [Conclusion](https://github.com/tc39/notes/blob/main/meetings/2023-03/mar-23.md#conclusion-5)
- Stage 3, conditionally on final review of cover grammar by Waldemar Horwat.
- Consensus on normative change to remove `await` identifier restriction for `using` declarations.
# TODO
@@ -1326,4 +1915,3 @@ The following is a high-level list of tasks to progress through each stage of th
[wrapper]: #wrapper
[callback-adapting wrapper]: #adapter
[single-use disposer]: #disposer
[async-using]: https://github.com/tc39/proposal-async-explicit-resource-management
Loading