diff --git a/README.md b/README.md index acb1711..e4a7804 100644 --- a/README.md +++ b/README.md @@ -47,14 +47,15 @@ In addition, we propose the addition of two disposable container objects to assi with managing multiple resources: - `DisposableStack` — A stack-based container of disposable resources. -- `AsyncDisposableStack` — A stack-based container of asynchronously disposable resources. +- ~~`AsyncDisposableStack` — A stack-based container of asynchronously disposable resources.~~ + _`AsyncDisposableStack`_ has been deferred to a [follow-on proposal][async-using]. ## Status -**Stage:** 2 \ +**Stage:** 3 \ **Champion:** Ron Buckton (@rbuckton) \ -**Last Presented:** October, 2021 ([slides](https://1drv.ms/p/s!AjgWTO11Fk-Tkfl3NHqg7QcpUoJcnQ?e=E2FsjF), -[notes](https://github.com/tc39/notes/blob/main/meetings/2021-10/oct-27.md#explicit-resource-management-update)) +**Last Presented:** November/December, 2022 ([slides](https://1drv.ms/p/s!AjgWTO11Fk-TkoJoXa_RG_DaDAaoqA?e=A1aYah), +notes TBA) _For more information see the [TC39 proposal process](https://tc39.es/process-document/)._ @@ -586,27 +587,27 @@ const { mut, cv } = data; ## Additions to `Symbol` -This proposal adds the properties `dispose` and `asyncDispose` to the `Symbol` constructor whose -values are the `@@dispose` and `@@asyncDispose` internal symbols, respectively: +This proposal adds the `dispose` property to the `Symbol` constructor, whose value is the `@@dispose` internal symbol: **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. Used by `AsyncDisposableStack` objects. | **TypeScript Definition** ```ts interface SymbolConstructor { readonly dispose: unique symbol; - readonly asyncDispose: unique symbol; } ``` -Even though this proposal no longer includes [novel syntax for async disposal](#out-of-scopedeferred), we still define +~~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. +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 @@ -676,22 +677,12 @@ 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` Interfaces +## The Common `Disposable` ~~and `AsyncDisposable`~~ Interface ### The `Disposable` Interface @@ -711,27 +702,14 @@ interface Disposable { } ``` -### The `AsyncDisposable` Interface +### ~~The `AsyncDisposable` Interface~~ -An object is _async disposable_ if it conforms to the following 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).** -| Property | Value | Requirements | -|:-|:-|:-| -| `@@asyncDispose` | An async function that performs explicit cleanup. | The function should return a `Promise`. | +## `DisposableStack` ~~and `AsyncDisposableStack`~~ container object -**TypeScript Definition** -```ts -interface AsyncDisposable { - /** - * Disposes of resources within this object. - */ - [Symbol.asyncDispose](): Promise; -} -``` - -## `DisposableStack` and `AsyncDisposableStack` container objects - -This proposal adds two global objects that can as containers to aggregate disposables, guaranteeing that every +This proposal adds a global object that can act as a container 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): @@ -789,59 +767,6 @@ class DisposableStack { [Symbol.toStringTag]; } - -class AsyncDisposableStack { - constructor(); - - /** - * Gets a value indicating whether the stack has been disposed. - * @returns {boolean} - */ - get disposed(); - - /** - * Alias for `[Symbol.asyncDispose]()`. - */ - disposeAsync(); - - /** - * Adds a resource to the top of the stack. Has no effect if provided `null` or `undefined`. - * @template {AsyncDisposable | Disposable | null | undefined} T - * @param {T} value - An `AsyncDisposable` or `Disposable` object, `null`, or `undefined`. - * @returns {T} The provided value. - */ - use(value); - - /** - * Adds a non-disposable resource and a disposal callback to the top of the stack. - * @template T - * @param {T} value - A resource to be disposed. - * @param {(value: T) => void | Promise} onDisposeAsync - A callback invoked to dispose the provided value. - * @returns {T} The provided value. - */ - adopt(value, onDisposeAsync); - - /** - * Adds a disposal callback to the top of the stack. - * @param {() => void | Promise} onDisposeAsync - A callback to evaluate when this object is disposed. - * @returns {void} - */ - defer(onDisposeAsync); - - /** - * Moves all resources currently in this stack into a new `AsyncDisposableStack`. - * @returns {AsyncDisposableStack} The new `AsyncDisposableStack`. - */ - move(); - - /** - * Asynchronously disposes of resources within this object. - * @returns {Promise} - */ - [Symbol.asyncDispose](); - - [Symbol.toStringTag]; -} ``` These classes provided the following capabilities: @@ -849,13 +774,16 @@ These classes provided the following capabilities: - Interoperation and customization - Assist in complex construction -NOTE: `DisposableStack` and `AsyncDisposableStack` are inspired by Python's -[`ExitStack`](https://docs.python.org/3/library/contextlib.html#contextlib.ExitStack) and -[`AsyncExitStack`](https://docs.python.org/3/library/contextlib.html#contextlib.AsyncExitStack). +> **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).** + ### Aggregation -The `DisposableStack` and `AsyncDisposableStack` classes provide the ability to aggregate multiple disposable resources +The `DisposableStack` ~~and `AsyncDisposableStack`~~ class provides 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 @@ -886,7 +814,7 @@ new SuppressedError( ### Interoperation and Customization -The `DisposableStack` and `AsyncDisposableStack` classes also provide the ability to create a disposable resource from a +The `DisposableStack` ~~and `AsyncDisposableStack`~~ class also provides 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: @@ -915,7 +843,7 @@ 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 @@ -1179,8 +1107,9 @@ be adapted by using `DisposableStack`. However, there are a number of APIs that 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` — `@@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. +> **NOTE: A summary of DOM APIs relevant to async disposal can be found in the +> [Async Explicit Resource Management][async-using] proposal.** + - `BroadcastChannel` — `@@dispose()` as an alias or [wrapper][] for `close()`. - `EventSource` — `@@dispose()` as an alias or [wrapper][] for `close()`. - `FileReader` — `@@dispose()` as an alias or [wrapper][] for `abort()`. @@ -1196,30 +1125,21 @@ offers suggestions for consideration. The actual implementation is at the discre ``` - `ImageBitmap` — `@@dispose()` as an alias or [wrapper][] for `close()`. - `IntersectionObserver` — `@@dispose()` as an alias or [wrapper][] for `disconnect()`. -- `MediaKeySession` — `@@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` — `@@dispose()` as an alias or [wrapper][] for `close()`. - `MutationObserver` — `@@dispose()` as an alias or [wrapper][] for `disconnect()`. - `PaymentRequest` — `@@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` — `@@dispose()` as an alias or [wrapper][] for `disconnect()`. -- `PushSubscription` — `@@asyncDispose()` as an alias or [wrapper][] for `unsubscribe()`. - `RTCPeerConnection` — `@@dispose()` as an alias or [wrapper][] for `close()`. - `RTCRtpTransceiver` — `@@dispose()` as an alias or [wrapper][] for `stop()`. -- `ReadableStream` — `@@asyncDispose()` as an alias or [wrapper][] for `cancel()`. - `ReadableStreamDefaultController` — `@@dispose()` as an alias or [wrapper][] for `close()`. - `ReadableStreamDefaultReader` — Either `@@dispose()` as an alias or [wrapper][] for `releaseLock()`, or - `@@asyncDispose()` as a [wrapper][] for `cancel()` (but probably not both). - `ResizeObserver` — `@@dispose()` as an alias or [wrapper][] for `disconnect()`. -- `ServiceWorkerRegistration` — `@@asyncDispose()` as a [wrapper][] for `unregister()`. - `SourceBuffer` — `@@dispose()` as a [wrapper][] for `abort()`. - `TransformStreamDefaultController` — `@@dispose()` as an alias or [wrapper][] for `terminate()`. - `WebSocket` — `@@dispose()` as a [wrapper][] for `close()`. - `Worker` — `@@dispose()` as an alias or [wrapper][] for `terminate()`. -- `WritableStream` — `@@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` — Either `@@dispose()` as an alias or [wrapper][] for `releaseLock()`, or - `@@asyncDispose()` as a [wrapper][] for `close()` (but probably not both). - `XMLHttpRequest` — `@@dispose()` as an alias or [wrapper][] for `abort()`. In addition, several new APIs could be considered that leverage this functionality: @@ -1257,6 +1177,9 @@ This proposal does not necessarily require immediate support in NodeJS, as exist 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.** + - Anything with `ref()` and `unref()` methods — A new method or API that produces a [single-use disposer][] for `ref()` and `unref()`. - Anything with `cork()` and `uncork()` methods — A new method or API that produces a [single-use disposer][] for @@ -1270,29 +1193,24 @@ consideration. The actual implementation is at the discretion of the NodeJS main - `dns.Resolver`, `dnsPromises.Resolver` — `@@dispose()` as an alias or [wrapper][] for `cancel()`. - `domain.Domain` — A new method or API that produces a [single-use disposer][] for `enter()` and `exit()`. - `events.EventEmitter` — A new method or API that produces a [single-use disposer][] for `on()` and `off()`. -- `fs.promises.FileHandle` — `@@disposeAsync()` as an alias or [wrapper][] for `close()`. -- `fs.Dir` — `@@disposeAsync()` as an alias or [wrapper][] for `close()`, `@@dispose()` as an alias or [wrapper][] - for `closeSync()`. - `fs.FSWatcher` — `@@dispose()` as an alias or [wrapper][] for `close()`. - `http.Agent` — `@@dispose()` as an alias or [wrapper][] for `destroy()`. -- `http.ClientRequest` — Either `@@dispose()` or `@@disposeAsync()` as an alias or [wrapper][] for `destroy()`. -- `http.Server` — `@@disposeAsync()` as a [callback-adapting wrapper][] for `close()`. -- `http.ServerResponse` — `@@disposeAsync()` as a [callback-adapting wrapper][] for `end()`. -- `http.IncomingMessage` — Either `@@dispose()` or `@@disposeAsync()` as an alias or [wrapper][] for `destroy()`. -- `http.OutgoingMessage` — Either `@@dispose()` or `@@disposeAsync()` as an alias or [wrapper][] for `destroy()`. -- `http2.Http2Session` — `@@disposeAsync()` as a [callback-adapting wrapper][] for `close()`. -- `http2.Http2Stream` — `@@disposeAsync()` as a [callback-adapting wrapper][] for `close()`. -- `http2.Http2Server` — `@@disposeAsync()` as a [callback-adapting wrapper][] for `close()`. -- `http2.Http2SecureServer` — `@@disposeAsync()` as a [callback-adapting wrapper][] for `close()`. -- `http2.Http2ServerRequest` — Either `@@dispose()` or `@@disposeAsync()` as an alias or [wrapper][] for +- `http.ClientRequest` — Either `@@dispose()` or `@@asyncDispose()` (see + [Async Explicit Resource Management][async-using]) as an alias or [wrapper][] for `destroy()`. +- `http.IncomingMessage` — Either `@@dispose()` or `@@asyncDispose()` (see + [Async Explicit Resource Management][async-using]) as an alias or [wrapper][] for `destroy()`. +- `http.OutgoingMessage` — Either `@@dispose()` or `@@asyncDispose()` (see + [Async Explicit Resource Management][async-using]) as an alias or [wrapper][] for `destroy()`. +- `http2.Http2ServerRequest` — Either `@@dispose()` or `@@asyncDispose()` (see + [Async Explicit Resource Management][async-using]) as an alias or [wrapper][] for `destroy()`. -- `http2.Http2ServerResponse` — `@@disposeAsync()` as a [callback-adapting wrapper][] for `end()`. -- `https.Server` — `@@disposeAsync()` as a [callback-adapting wrapper][] for `close()`. - `inspector` — A new API that produces a [single-use disposer][] for `open()` and `close()`. -- `stream.Writable` — Either `@@dispose()` or `@@disposeAsync()` as an alias or [wrapper][] for `destroy()` or - `@@disposeAsync` only as a [callback-adapting wrapper][] for `end()` (depending on whether the disposal behavior +- `stream.Writable` — Either `@@dispose()` or `@@asyncDispose()` (see + [Async Explicit Resource Management][async-using]) 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` — Either `@@dispose()` or `@@disposeAsync()` as an alias or [wrapper][] for `destroy()`. +- `stream.Readable` — Either `@@dispose()` or `@@asyncDispose()` (see + [Async Explicit Resource Management][async-using]) as an alias or [wrapper][] for `destroy()`. - ... and many others in `net`, `readline`, `tls`, `udp`, and `worker_threads`. # Out-of-Scope/Deferred @@ -1300,11 +1218,13 @@ consideration. The actual implementation is at the discretion of the NodeJS main 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: -- Bindingless [`using void`](./future/using-void-declaration.md) declarations (i.e., `using void = expr`). -- Block-style [`using` statements](./future/using-statement.md) (i.e., `using (x = expr) {}`) for sync disposables. -- RAII-style [`using await` declarations](./future/using-await-declaration.md) (i.e., `using await id = expr`) for async - disposables. -- Block-style [`using await` statements]() (i.e., `using await (x = expr) {}`) for async disposables. +- RAII-style [`async using` declarations][async-using] (i.e., `async using id = expr`) for async + disposables — _Postponed to [Follow-on Proposal][async-using]_ +- `Symbol.asyncDispose` — _Postponed to [Follow-on Proposal][async-using]_ +- `AsyncDisposableStack` — _Postponed to [Follow-on Proposal][async-using]_ +- Bindingless [`using void`](./future/using-void-declaration.md) declarations (i.e., `using void = expr`) — _Postponed to Follow-on Proposal_ +- Block-style [`using` statements](./future/using-statement.md) (i.e., `using (x = expr) {}`) for sync disposables — - _Withdrawn_. +- Block-style [`using await` statements](./future/using-await-statement.md) (i.e., `using await (x = expr) {}`) for async disposables - _Withdrawn_. # Meeting Notes @@ -1321,9 +1241,16 @@ important characteristics for the ECMAScript to employ in the future and may be - YK (@wycatz) & WH (@waldemarhorwat) will be stage 3 reviewers * [TC39 October 10th, 2021](https://github.com/tc39/notes/blob/main/meetings/2021-10/oct-27.md#explicit-resource-management-update) - [Conclusion](https://github.com/tc39/notes/blob/main/meetings/2021-10/oct-27.md#conclusionresolution-1) - - Status Update only - - WH Continuing to review - - SYG (@syg) added as reviewer + - Status Update only + - WH Continuing to review + - SYG (@syg) added as reviewer +* TC39 December 1st, 2022 (notes TBA) + - Conclusion + - `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 + proposal. # TODO @@ -1344,10 +1271,10 @@ The following is a high-level list of tasks to progress through each stage of th ### Stage 3 Entrance Criteria * [x] [Complete specification text][Specification]. -* [ ] Designated reviewers have signed off on the current spec text: - * [ ] [Waldemar Horwat][Stage3Reviewer1] has [signed off][Stage3Reviewer1SignOff] - * [ ] [Shu-yu Guo][Stage3Reviewer2] has [signed off][Stage3Reviewer2SignOff] -* [ ] The [ECMAScript editor][Stage3Editor] has [signed off][Stage3EditorSignOff] on the current spec text. +* [x] Designated reviewers have signed off on the current spec text: + * [x] [Waldemar Horwat][Stage3Reviewer1] has [signed off][Stage3Reviewer1SignOff] + * [x] [Shu-yu Guo][Stage3Reviewer2] has [signed off][Stage3Reviewer2SignOff] +* [x] The [ECMAScript editor][Stage3Editor] has [signed off][Stage3EditorSignOff] on the current spec text. ### Stage 4 Entrance Criteria @@ -1394,3 +1321,4 @@ 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 \ No newline at end of file diff --git a/future/using-await-declaration.md b/future/using-await-declaration.md deleted file mode 100644 index dc41d29..0000000 --- a/future/using-await-declaration.md +++ /dev/null @@ -1,166 +0,0 @@ -# `using await` Declarations for ECMAScript - -A `using await` declaration is a variation of the [`using` declaration](../README.md) that is designed to work with -Async Disposable objects, which allow async logic to be evaluated and awaited during disposal. A `using await` -declaration is only permitted in an `[+Await]` context such as the top level of a _Module_ or inside the body of an -async function. - -# Example - -```js -class Logger { - write(message) { ... } - ... - #disposed = false; - async [Symbol.asyncDispose]() { - if (!this.#disposed) { - this.#disposed = true; - await this.#flush(); // flush any pending log file writes asynchronously. - this.#close(); // close file handle. - } - } -} - -async function main() { - using await logger = new Logger(); // create logger, async dispose at end of block - logger.write("started"); - ... - logger.write("done"); - - // implicit `await logger[Symbol.asyncDispose]()` when block exits regardless as to - // whether by `return`, `throw`, `break`, or `continue`. -} -``` - -# Status - -**Status:** Out of scope - -The `using await` declaration has been postponed and deemed out of scope for the original proposal. This is primarily -due to concerns about introducing an implicit `await` without a clear marker at the start or end of the containing -block. - -# Alternatives - -This could also potentially be addressed by the [`using await` statement](./using-await-statement.md). - -# Postponement Implications - -Postponing `using await` declarations without also introducing `using await` statements means that we lose the ability -to declaratively register disposal of resources that require async cleanup steps. This affects use cases such as -three-phase commit (3PC) transactions and some async file I/O (i.e., async flush). These still can be accomplished -imperatively via `await obj[Symbol.asyncDispose]()`, but the lack of a syntactic declaration reduces their utility. - -# Explainer Snapshot - -The following sections were originally part of the [explainer](../README.md). - -> # Syntax -> -> ## `using` Declarations -> -> ```js -> // for an asynchronously-disposed resource (block scoped): -> using await x = expr1; // resource w/ local binding -> using await void = expr; // resource w/o local binding -> using await y = expr2, void = expr3, z = expr3; // multiple resources -> ``` -> -> # Grammar -> -> ```grammarkdown -> UsingDeclaration[In, Yield, Await] : -> ... -> [+Await] `using` [no LineTerminator here] `await` BindingList[?In, ?Yield, +Await, +Using] `;` -> ``` -> -> # Semantics -> -> ## `using` Declarations -> -> ### `using await` Declarations and Values Without `[Symbol.asyncDispose]` -> -> If a resource does not have either a callable `[Symbol.asyncDispose]` member or a callable `[Symbol.dispose]` member, -> a `TypeError` would be thrown **immediately** when the resource is tracked. -> -> ### `using await` Declarations in _AsyncFunction_, _AsyncGeneratorFunction_, or _Module_ -> -> In an _AsyncFunction_, _AsyncGeneratorFunction_, _AsyncArrowFunction_, or the top-level of a _Module_, when we -> evaluate a `using await` declaration we first look for a `[Symbol.asyncDispose]` method before looking for a -> `[Symbol.dispose]` method. At the end of the containing function body, _Block_, or _Module_, if the method returns a -> value other than `undefined`, we **Await** the value before exiting: -> -> ```js -> async function f() { -> ... // (1) -> using await x = expr; -> ... // (2) -> } -> ``` -> -> Is semantically equivalent to the following transposed representation: -> -> -> ```js -> async function f() { -> const $$try = { stack: [], exception: undefined }; -> try { -> ... // (1) -> -> const x = expr; -> if (x !== null && x !== undefined) { -> let $$dispose = x[Symbol.asyncDispose]; -> if ($$dispose === undefined) { -> $$dispose = x[Symbol.dispose]; -> } -> if (typeof $$dispose !== "function") { -> throw new TypeError(); -> } -> $$try.stack.push({ value: x, dispose: $$dispose }); -> } -> -> ... // (2) -> } -> catch ($$error) { -> $$try.exception = { cause: $$error }; -> } -> finally { -> const $$errors = []; -> while ($$try.stack.length) { -> const { value: $$expr, dispose: $$dispose } = $$try.stack.pop(); -> try { -> const $$result = $$dispose.call($$expr); -> if ($$result !== undefined) { -> await $$result; -> } -> } -> catch ($$error) { -> $$errors.push($$error); -> } -> } -> if ($$errors.length > 0) { -> throw new AggregateError($$errors, undefined, $$try.exception); -> } -> if ($$try.exception) { -> throw $$try.exception.cause; -> } -> } -> } -> ``` -> -> # Examples -> -> The following show examples of using this proposal with various APIs, assuming those APIs adopted this proposal. -> -> ### Transactional Consistency (ACID/3PC) -> ```js -> // roll back transaction if either action fails -> { -> using await tx = transactionManager.startTransaction(account1, account2); -> await account1.debit(amount); -> await account2.credit(amount); -> -> // mark transaction success -> tx.succeeded = true; -> } // transaction is committed -> ``` diff --git a/spec.emu b/spec.emu index 804a6fe..169c6d5 100644 --- a/spec.emu +++ b/spec.emu @@ -28,7 +28,7 @@ @@ -62,17 +62,6 @@ contributors: Ron Buckton, Ecma International Value and Purpose - - - @@asyncDispose - - - `"Symbol.asyncDispose"` - - - A method that performs explicit resource cleanup on an object. Called by the semantics of AsyncDisposableStack objects. - - @@dispose @@ -149,17 +138,6 @@ contributors: Ron Buckton, Ecma International The prototype of Array iterator objects () - - - %AsyncDisposableStack% - - - `AsyncDisposableStack` - - - The AsyncDisposableStack constructor () - - %AsyncFromSyncIteratorPrototype% @@ -938,10 +916,10 @@ contributors: Ron Buckton, Ecma International [[Hint]] - ~sync-dispose~ or ~async-dispose~. + ~sync-dispose~. - Indicates whether the resources was added by a `using` declaration or DisposableStack object (~sync-dispose~) or an AsyncDisposableStack object (~async-dispose~). + Indicates that the resource was added by a `using` declaration or DisposableStack object. @@ -958,6 +936,9 @@ contributors: Ron Buckton, Ecma International + + Currently, the only allowed value for [[Hint]] is ~sync-dispose~. + @@ -965,7 +946,7 @@ contributors: Ron Buckton, Ecma International AddDisposableResource ( _disposable_ : an object with a [[DisposableResourceStack]] internal slot, _V_ : an ECMAScript language value, - _hint_ : either ~sync-dispose~ or ~async-dispose~, + _hint_ : ~sync-dispose~, optional _method_ : a function object, ) @@ -985,13 +966,16 @@ contributors: Ron Buckton, Ecma International 1. Append _resource_ to _disposable_.[[DisposableResourceStack]]. 1. Return NormalCompletion(~empty~). + + Currently, the only allowed value for _hint_ is ~sync-dispose~. +

CreateDisposableResource ( _V_ : an Object or *undefined*, - _hint_ : either ~sync-dispose~ or ~async-dispose~, + _hint_ : ~sync-dispose~, optional _method_ : a function object, )

@@ -1005,23 +989,22 @@ contributors: Ron Buckton, Ecma International 1. If IsCallable(_method_) is *false*, throw a *TypeError* exception. 1. Return the DisposableResource Record { [[ResourceValue]]: _V_, [[Hint]]: _hint_, [[DisposeMethod]]: _method_ }. + + Currently, the only allowed value for _hint_ is ~sync-dispose~. +

GetDisposeMethod ( _V_ : an Object, - _hint_ : either ~sync-dispose~ or ~async-dispose~, + _hint_ : ~sync-dispose~, )

- 1. If _hint_ is ~async-dispose~, then - 1. Let _method_ be ? GetMethod(_V_, @@asyncDispose). - 1. If _method_ is *undefined*, then - 1. Set _method_ to ? GetMethod(_V_, @@dispose). - 1. Else, - 1. Let _method_ be ? GetMethod(_V_, @@dispose). + 1. Assert: _hint_ is ~sync-dispose~. + 1. Let _method_ be ? GetMethod(_V_, @@dispose). 1. Return _method_.
@@ -1030,15 +1013,14 @@ contributors: Ron Buckton, Ecma International

Dispose ( _V_ : an Object or *undefined*, - _hint_ : either ~sync-dispose~ or ~async-dispose~, + _hint_ : ~sync-dispose~, _method_ : a function object, )

- 1. Let _result_ be ? Call(_method_, _V_). - 1. If _hint_ is ~async-dispose~ and _result_ is not *undefined*, then - 1. Perform ? Await(_result_). + 1. Assert: _hint_ is ~sync-dispose~. + 1. Perform ? Call(_method_, _V_). 1. Return *undefined*. @@ -3493,36 +3475,6 @@ contributors: Ron Buckton, Ecma International - - -

The %AsyncIteratorPrototype% Object

- - - -

%AsyncIteratorPrototype% [ @@asyncDispose ] ( )

-

The following steps are taken:

- - 1. Let _O_ be the *this* value. - 1. Let _promiseCapability_ be ! NewPromiseCapability(%Promise%). - 1. Let _return_ be GetMethod(_O_, `"return"`). - 1. IfAbruptRejectPromise(_return_, _promiseCapability_). - 1. If _return_ is *undefined*, then - 1. Perform ! Call(_promiseCapability_.[[Resolve]], *undefined*, « *undefined* »). - 1. Else, - 1. Let _result_ be Call(_return_, _O_, « *undefined* »). - 1. IfAbruptRejectPromise(_result_, _promiseCapability_). - 1. Let _resultWrapper_ be Completion(PromiseResolve(%Promise%, _result_)). - 1. IfAbruptRejectPromise(_resultWrapper_, _promiseCapability_). - 1. Let _unwrap_ be a new Abstract Closure that performs the following steps when called: - 1. Return *undefined*. - 1. Let _onFulfilled_ be CreateBuiltinFunction(_unwrap_, 1, "", « »). - 1. Perform PerformPromiseThen(_resultWrapper_, _onFulfilled_, *undefined*, _promiseCapability_). - 1. Return _promiseCapability_.[[Promise]]. - -

The value of the *"name"* property of this function is *"[Symbol.asyncDispose]"*.

-
-
-
@@ -3531,7 +3483,6 @@ contributors: Ron Buckton, Ecma International

Common Resource Management Interfaces

-

An interface is a set of property keys whose associated values match a specific specification. Any object that provides all the properties as described by an interface's specification conforms to that interface. An interface is not represented by a distinct object. There may be many separately implemented objects that conform to any interface. An individual object may conform to multiple interfaces.

The Disposable Interface

@@ -3568,39 +3519,6 @@ contributors: Ron Buckton, Ecma International
- -

The AsyncDisposable Interface

-

The AsyncDisposable interface includes the property described in :

- - - - - - - - - - - - - - -
- Property - - Value - - Requirements -
- `@@asyncDispose` - - A function that returns a promise. - -

Invoking this method notifies the AsyncDisposable object that the caller does not intend to continue to use this object. This method should perform any necessary logic to perform explicit clean-up of the resource including, but not limited to, file system handles, streams, host objects, etc. When an exception is thrown from this method, it typically means that the resource could not be explicitly freed. An AsyncDisposable object is not considered "disposed" until the resulting Promise has been fulfilled.

-

If called more than once on the same object, the function should not throw an exception. However, this requirement is not enforced.

-
-
-
@@ -3803,212 +3721,6 @@ contributors: Ron Buckton, Ecma International - - -

AsyncDisposableStack Objects

-

An AsyncDisposableStack is an object that can be used to contain one or more resources that should be asynchronously disposed together.

-

Any AsyncDisposableStack object is in one of two mutually exclusive states: disposed or pending:

-
    -
  • An async-disposable stack `d` is pending if `d[Symbol.asyncDispose]()` has yet to be invoked for `d`.
  • -
  • An async-disposable stack `d` is disposed if `d[Symbol.asyncDispose]()` has already been invoked once for `d`.
  • -
- - -

The AsyncDisposableStack Constructor

-

The AsyncDisposableStack constructor:

-
    -
  • is %AsyncDisposableStack%.
  • -
  • is the initial value of the *"AsyncDisposableStack"* property of the global object.
  • -
  • creates and initializes a new AsyncDisposableStack when called as a constructor.
  • -
  • is not intended to be called as a function and will throw an exception when called in that manner.
  • -
  • may be used as the value in an `extends` clause of a class definition. Subclass constructors that intend to inherit the specified AsyncDisposableStack behaviour must include a `super` call to the AsyncDisposableStack constructor to create and initialize the subclass instance with the internal state necessary to support the `AsyncDisposableStack` and `AsyncDisposableStack.prototype` built-in methods.
  • -
- - -

AsyncDisposableStack ( )

-

When the `AsyncDisposableStack` function is called, the following steps are taken:

- - 1. If NewTarget is *undefined*, throw a *TypeError* exception. - 1. Let _asyncDisposableStack_ be ? OrdinaryCreateFromConstructor(NewTarget, *"%AsyncDisposableStack.prototype%"*, « [[AsyncDisposableState]], [[DisposableResourceStack]] »). - 1. Set _asyncDisposableStack_.[[AsyncDisposableState]] to ~pending~. - 1. Set _asyncDisposableStack_.[[DisposableResourceStack]] to a new empty List. - 1. Return _asyncDisposableStack_. - -
-
- - -

Properties of the AsyncDisposableStack Constructor

-

The AsyncDisposableStack constructor:

-
    -
  • Has a [[Prototype]] internal slot whose value is %Function.prototype%.
  • -
-
- - -

Properties of the AsyncDisposableStack Prototype Object

-

The AsyncDisposableStack prototype object:

-
    -
  • is %AsyncDisposableStack.prototype%.
  • -
  • has a [[Prototype]] internal slot whose value is %Object.prototype%.
  • -
  • is an ordinary object.
  • -
  • does not have an [[AsyncDisposableState]] internal slot or any of the other internal slots of AsyncDisposableStack instances.
  • -
- - -

get AsyncDisposableStack.prototype.disposed

-

`AsyncDisposableStack.prototype.disposed` is an accessor property whose set accessor function is *undefined*. Its get accessor function performs the following steps:

- - 1. Let _asyncDisposableStack_ be the *this* value. - 1. Perform ? RequireInternalSlot(_asyncDisposableStack_, [[AsyncDisposableState]]). - 1. If _asyncDisposableStack_.[[AsyncDisposableState]] is ~disposed~, return *true*. - 1. Otherwise, return *false*. - -
- - -

AsyncDisposableStack.prototype.disposeAsync()

-

When the `disposeAsync` method is called, the following steps are taken:

- - 1. Let _asyncDisposableStack_ be the *this* value. - 1. Let _promiseCapability_ be ! NewPromiseCapability(%Promise%). - 1. If _asyncDisposableStack_ does not have an [[AsyncDisposableState]] internal slot, then - 1. Perform ! Call(_promiseCapability_.[[Reject]], *undefined*, « a newly created *TypeError* object »). - 1. Return _promiseCapability_.[[Promise]]. - 1. If _asyncDisposableStack_.[[AsyncDisposableState]] is ~disposed~, then - 1. Perform ! Call(_promiseCapability_.[[Resolve]], *undefined*, « *undefined* »). - 1. Return _promiseCapability_.[[Promise]]. - 1. Set _asyncDisposableStack_.[[AsyncDisposableState]] to ~disposed~. - 1. Let _result_ be DisposeResources(_asyncDisposableStack_, NormalCompletion(*undefined*)). - 1. IfAbruptRejectPromise(_result_, _promiseCapability_). - 1. Perform ! Call(_promiseCapability_.[[Resolve]], *undefined*, « _result_ »). - 1. Return _promiseCapability_.[[Promise]]. - -
- - -

AsyncDisposableStack.prototype.use( _value_ )

-

When the `use` function is called with one argument, the following steps are taken:

- - 1. Let _asyncDisposableStack_ be the *this* value. - 1. Perform ? RequireInternalSlot(_asyncDisposableStack_, [[AsyncDisposableState]]). - 1. If _asyncDisposableStack_.[[AsyncDisposableState]] is ~disposed~, throw a *ReferenceError* exception. - 1. If _value_ is neither *null* nor *undefined*, then - 1. If Type(_value_) is not Object, throw a *TypeError* exception. - 1. Let _method_ be GetDisposeMethod(_value_, ~async-dispose~). - 1. If _method_ is *undefined*, then - 1. Throw a *TypeError* exception. - 1. Else, - 1. Perform ? AddDisposableResource(_disposableStack_, _value_, ~async-dispose~, _method_). - 1. Return _value_. - -
- - -

AsyncDisposableStack.prototype.adopt( _value_, _onDisposeAsync_ )

-

When the `adopt` function is called with two arguments, the following steps are taken:

- - 1. Let _asyncDisposableStack_ be the *this* value. - 1. Perform ? RequireInternalSlot(_asyncDisposableStack_, [[AsyncDisposableState]]). - 1. If _asyncDisposableStack_.[[AsyncDisposableState]] is ~disposed~, throw a *ReferenceError* exception. - 1. If IsCallable(_onDisposeAsync_) is *false*, throw a *TypeError* exception. - 1. Let _F_ be a new built-in function object as defined in . - 1. Set _F_.[[Argument]] to _value_. - 1. Set _F_.[[OnDisposeAsyncCallback]] to _onDisposeAsync_. - 1. Perform ? AddDisposableResource(_asyncDisposableStack_, *undefined*, ~async-dispose~, _F_). - 1. Return _value_. - - - -

AsyncDisposableStack Adopt Callback Functions

-

An AsyncDisposableStack adopt callback function is an anonymous built-in function that has [[Argument]] and [[OnDisposeAsyncCallback]] internal slots.

-

When an AsyncDisposableStack adopt callback function is called, the following steps are taken:

- - 1. Let _F_ be the active function object. - 1. Assert: IsCallable(_F_.[[OnDisposeAsyncCallback]]) is *true*. - 1. Return Call(_F_.[[OnDisposeAsyncCallback]], *undefined*, « _F_.[[Argument]] »). - -
-
- - -

AsyncDisposableStack.prototype.defer( _onDisposeAsync_ )

-

When the `defer` function is called with one argument, the following steps are taken:

- - 1. Let _asyncDisposableStack_ be the *this* value. - 1. Perform ? RequireInternalSlot(_asyncDisposableStack_, [[AsyncDisposableState]]). - 1. If _asyncDisposableStack_.[[AsyncDisposableState]] is ~disposed~, throw a *ReferenceError* exception. - 1. If IsCallable(_onDisposeAsync_) is *false*, throw a *TypeError* exception. - 1. Perform ? AddDisposableResource(_asyncDisposableStack_, *undefined*, ~async-dispose~, _onDisposeAsync_). - 1. Return *undefined*. - -
- - -

AsyncDisposableStack.prototype.move()

-

When the `move` function is called, the following steps are taken:

- - 1. Let _asyncDisposableStack_ be the *this* value. - 1. Perform ? RequireInternalSlot(_asyncDisposableStack_, [[AsyncDisposableState]]). - 1. If _asyncDisposableStack_.[[AsyncDisposableState]] is ~disposed~, throw a *ReferenceError* exception. - 1. Let _newAsyncDisposableStack_ be ? OrdinaryCreateFromConstructor(%AsyncDisposableStack%, *"%AsyncDisposableStack.prototype%"*, « [[AsyncDisposableState]], [[DisposableResourceStack]] »). - 1. Set _newAsyncDisposableStack_.[[AsyncDisposableState]] to ~pending~. - 1. Set _newAsyncDisposableStack_.[[DisposableResourceStack]] to _asyncDisposableStack_.[[DisposableResourceStack]]. - 1. Set _asyncDisposableStack_.[[DisposableResourceStack]] to a new empty List. - 1. Set _asyncDisposableStack_.[[AsyncDisposableState]] to ~disposed~. - 1. Return _newAsyncDisposableStack_. - -
- - -

AsyncDisposableStack.prototype [ @@asyncDispose ] ()

-

The initial value of the @@asyncDispose property is %AsyncDisposableStack.prototype.disposeAsync%, defined in .

-
- - -

AsyncDisposableStack.prototype [ @@toStringTag ]

-

The initial value of the `@@toStringTag` property is the String value *"AsyncDisposableStack"*.

-

This property has the attributes { [[Writable]]: *false*, [[Enumerable]]: *false*, [[Configurable]]: *true* }.

-
-
- - -

Properties of AsyncDisposableStack Instances

-

AsyncDisposableStack instances are ordinary objects that inherit properties from the AsyncDisposableStack prototype object (the intrinsic %AsyncDisposableStack.prototype%). AsyncDisposableStack instances are initially created with internal slots described in .

- - - - - - - - - - - - - - - - -
- Internal Slot - - Description -
- [[AsyncDisposableState]] - - One of ~pending~ or ~disposed~. Governs how a disposable stack will react to incoming calls to its `@@asyncDispose` method. -
- [[DisposableResourceStack]] - - A List of DisposableResource records. -
-
-
-
-
-

Generator Objects