├── .gitattributes ├── .github └── workflows │ ├── build.yml │ └── pr.yml ├── .gitignore ├── .vscode ├── extensions.json ├── settings.json └── tasks.json ├── .yo-rc.json ├── LICENSE ├── README.md ├── future ├── using-await-statement.md ├── using-statement.md └── using-void-declaration.md ├── gulpfile.js ├── package-lock.json ├── package.json └── spec.emu /.gitattributes: -------------------------------------------------------------------------------- 1 | index.html -diff merge=ours 2 | spec.js -diff merge=ours 3 | spec.css -diff merge=ours -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Publish Spec to gh-pages 2 | on: 3 | push: 4 | branches: [ main ] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - run: npm install --legacy-peer-deps 11 | - run: npm run compile 12 | - name: Deploy 13 | uses: JamesIves/github-pages-deploy-action@4.1.4 14 | with: 15 | branch: gh-pages 16 | folder: docs 17 | clean-exclude: | 18 | pr 19 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: Publish PR to gh-pages/pr/ 2 | on: 3 | pull_request: 4 | branches: [ main ] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | if: ${{ github.event.number }} 9 | steps: 10 | - uses: actions/checkout@v2 11 | - run: npm install --legacy-peer-deps 12 | - run: npm run compile 13 | - name: Deploy 14 | uses: JamesIves/github-pages-deploy-action@4.1.4 15 | with: 16 | branch: gh-pages 17 | folder: docs 18 | target-folder: pr/${{ github.event.number }}/ 19 | - id: get-preview-url 20 | name: Get preview url 21 | run: echo "::set-output name=preview-url::https://tc39.es/$(basename $GITHUB_REPOSITORY)/pr/${{ github.event.number }}" 22 | shell: bash 23 | - name: Post Preview Comment 24 | uses: phulsechinmay/rewritable-pr-comment@v0.3.0 25 | with: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | COMMENT_IDENTIFIER: tc39_pr_preview_comment 28 | message: | 29 | A preview of this PR can be found at ${{ steps.get-preview-url.outputs.preview-url }}. 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | docs 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "rbuckton.grammarkdown-vscode", 4 | "rbuckton.ecmarkup-vscode" 5 | ] 6 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[markdown]": { 3 | "files.trimTrailingWhitespace": false 4 | }, 5 | "[html]": { 6 | "editor.insertSpaces": true, 7 | "editor.tabSize": 2, 8 | }, 9 | "[ecmarkup]": { 10 | "editor.insertSpaces": true, 11 | "editor.tabSize": 2, 12 | }, 13 | "files.associations": { 14 | // "*.html": "ecmarkup", 15 | "*.emu": "ecmarkup" 16 | } 17 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "gulp", 8 | "task": "default", 9 | "group": { 10 | "kind": "build", 11 | "isDefault": true 12 | } 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-ecmascript-proposal": { 3 | "promptValues": { 4 | "authorName": "Ron Buckton", 5 | "authorEmail": "ron.buckton@microsoft.com", 6 | "authorGithub": "rbuckton", 7 | "hasChampion": true, 8 | "githubAccount": "rbuckton", 9 | "spec": "https://rbuckton.github.io/proposal-using", 10 | "stage": "0", 11 | "sections": [ 12 | "syntax", 13 | "examples", 14 | "todo", 15 | "semantics", 16 | "examples", 17 | "api", 18 | "grammar", 19 | "todo", 20 | "references", 21 | "prior-discussion" 22 | ], 23 | "championName": "Ron Buckton", 24 | "championGithub": "rbuckton" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, Ron Buckton, Ecma International 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ECMAScript Explicit Resource Management 2 | 3 | > **NOTE:** This proposal has subsumed the [Async Explicit Resource Management](https://github.com/tc39/proposal-async-explicit-resource-management) 4 | > proposal. This proposal repository should be used for further discussion of both sync and async of explicit resource 5 | > management. 6 | 7 | This proposal intends to address a common pattern in software development regarding 8 | the lifetime and management of various resources (memory, I/O, etc.). This pattern 9 | generally includes the allocation of a resource and the ability to explicitly 10 | release critical resources. 11 | 12 | For example, ECMAScript Generator Functions and Async Generator Functions expose this pattern through the 13 | `return` method, as a means to explicitly evaluate `finally` blocks to ensure 14 | user-defined cleanup logic is preserved: 15 | 16 | ```js 17 | // sync generators 18 | function * g() { 19 | const handle = acquireFileHandle(); // critical resource 20 | try { 21 | ... 22 | } 23 | finally { 24 | handle.release(); // cleanup 25 | } 26 | } 27 | 28 | const obj = g(); 29 | try { 30 | const r = obj.next(); 31 | ... 32 | } 33 | finally { 34 | obj.return(); // calls finally blocks in `g` 35 | } 36 | ``` 37 | 38 | ```js 39 | // async generators 40 | async function * g() { 41 | const handle = acquireStream(); // critical resource 42 | try { 43 | ... 44 | } 45 | finally { 46 | await stream.close(); // cleanup 47 | } 48 | } 49 | 50 | const obj = g(); 51 | try { 52 | const r = await obj.next(); 53 | ... 54 | } 55 | finally { 56 | await obj.return(); // calls finally blocks in `g` 57 | } 58 | ``` 59 | 60 | As such, we propose the adoption of a novel syntax to simplify this common pattern: 61 | 62 | ```js 63 | // sync disposal 64 | function * g() { 65 | using handle = acquireFileHandle(); // block-scoped critical resource 66 | } // cleanup 67 | 68 | { 69 | using obj = g(); // block-scoped declaration 70 | const r = obj.next(); 71 | } // calls finally blocks in `g` 72 | ``` 73 | 74 | ```js 75 | // async disposal 76 | async function * g() { 77 | using stream = acquireStream(); // block-scoped critical resource 78 | ... 79 | } // cleanup 80 | 81 | { 82 | await using obj = g(); // block-scoped declaration 83 | const r = await obj.next(); 84 | } // calls finally blocks in `g` 85 | ``` 86 | 87 | In addition, we propose the addition of two disposable container objects to assist 88 | with managing multiple resources: 89 | 90 | - `DisposableStack` — A stack-based container of disposable resources. 91 | - `AsyncDisposableStack` — A stack-based container of asynchronously disposable resources. 92 | 93 | ## Status 94 | 95 | **Stage:** 3 \ 96 | **Champion:** Ron Buckton (@rbuckton) \ 97 | **Last Presented:** March, 2023 ([slides](https://1drv.ms/p/s!AjgWTO11Fk-Tkodu1RydtKh2ZVafxA?e=yasS3Y), 98 | [notes #1](https://github.com/tc39/notes/blob/main/meetings/2023-03/mar-21.md#async-explicit-resource-management), 99 | [notes #2](https://github.com/tc39/notes/blob/main/meetings/2023-03/mar-23.md#async-explicit-resource-management-again)) 100 | 101 | _For more information see the [TC39 proposal process](https://tc39.es/process-document/)._ 102 | 103 | ## Authors 104 | 105 | - Ron Buckton (@rbuckton) 106 | 107 | # Motivations 108 | 109 | This proposal is motivated by a number of cases: 110 | 111 | - Inconsistent patterns for resource management: 112 | - ECMAScript Iterators: `iterator.return()` 113 | - WHATWG Stream Readers: `reader.releaseLock()` 114 | - NodeJS FileHandles: `handle.close()` 115 | - Emscripten C++ objects handles: `Module._free(ptr) obj.delete() Module.destroy(obj)` 116 | - Avoiding common footguns when managing resources: 117 | ```js 118 | const reader = stream.getReader(); 119 | ... 120 | reader.releaseLock(); // Oops, should have been in a try/finally 121 | ``` 122 | - Scoping resources: 123 | ```js 124 | const handle = ...; 125 | try { 126 | ... // ok to use `handle` 127 | } 128 | finally { 129 | handle.close(); 130 | } 131 | // not ok to use `handle`, but still in scope 132 | ``` 133 | - Avoiding common footguns when managing multiple resources: 134 | ```js 135 | const a = ...; 136 | const b = ...; 137 | try { 138 | ... 139 | } 140 | finally { 141 | a.close(); // Oops, issue if `b.close()` depends on `a`. 142 | b.close(); // Oops, `b` never reached if `a.close()` throws. 143 | } 144 | ``` 145 | - Avoiding lengthy code when managing multiple resources correctly: 146 | ```js 147 | // sync disposal 148 | { // block avoids leaking `a` or `b` to outer scope 149 | const a = ...; 150 | try { 151 | const b = ...; 152 | try { 153 | ... 154 | } 155 | finally { 156 | b.close(); // ensure `b` is closed before `a` in case `b` 157 | // depends on `a` 158 | } 159 | } 160 | finally { 161 | a.close(); // ensure `a` is closed even if `b.close()` throws 162 | } 163 | } 164 | // both `a` and `b` are out of scope 165 | ``` 166 | Compared to: 167 | ```js 168 | // avoids leaking `a` or `b` to outer scope 169 | // ensures `b` is disposed before `a` in case `b` depends on `a` 170 | // ensures `a` is disposed even if disposing `b` throws 171 | using a = ..., b = ...; 172 | ... 173 | ``` 174 | ```js 175 | // async sync disposal 176 | { // block avoids leaking `a` or `b` to outer scope 177 | const a = ...; 178 | try { 179 | const b = ...; 180 | try { 181 | ... 182 | } 183 | finally { 184 | await b.close(); // ensure `b` is closed before `a` in case `b` 185 | // depends on `a` 186 | } 187 | } 188 | finally { 189 | await a.close(); // ensure `a` is closed even if `b.close()` throws 190 | } 191 | } 192 | // both `a` and `b` are out of scope 193 | ``` 194 | Compared to: 195 | ```js 196 | // avoids leaking `a` or `b` to outer scope 197 | // ensures `b` is disposed before `a` in case `b` depends on `a` 198 | // ensures `a` is disposed even if disposing `b` throws 199 | await using a = ..., b = ...; 200 | ... 201 | ``` 202 | - Non-blocking memory/IO applications: 203 | ```js 204 | import { ReaderWriterLock } from "..."; 205 | const lock = new ReaderWriterLock(); 206 | 207 | export async function readData() { 208 | // wait for outstanding writer and take a read lock 209 | using lockHandle = await lock.read(); 210 | ... // any number of readers 211 | await ...; 212 | ... // still in read lock after `await` 213 | } // release the read lock 214 | 215 | export async function writeData(data) { 216 | // wait for all readers and take a write lock 217 | using lockHandle = await lock.write(); 218 | ... // only one writer 219 | await ...; 220 | ... // still in write lock after `await` 221 | } // release the write lock 222 | ``` 223 | - Potential for use with the [Fixed Layout Objects Proposal](https://github.com/tc39/proposal-structs) and 224 | `shared struct`: 225 | ```js 226 | // main.js 227 | shared struct class SharedData { 228 | ready = false; 229 | processed = false; 230 | } 231 | 232 | const worker = new Worker('worker.js'); 233 | const m = new Atomics.Mutex(); 234 | const cv = new Atomics.ConditionVariable(); 235 | const data = new SharedData(); 236 | worker.postMessage({ m, cv, data }); 237 | 238 | // send data to worker 239 | { 240 | // wait until main can get a lock on 'm' 241 | using lck = m.lock(); 242 | 243 | // mark data for worker 244 | data.ready = true; 245 | console.log("main is ready"); 246 | 247 | } // unlocks 'm' 248 | 249 | // notify potentially waiting worker 250 | cv.notifyOne(); 251 | 252 | { 253 | // reacquire lock on 'm' 254 | using lck = m.lock(); 255 | 256 | // release the lock on 'm' and wait for the worker to finish processing 257 | cv.wait(m, () => data.processed); 258 | 259 | } // unlocks 'm' 260 | ``` 261 | 262 | ```js 263 | // worker.js 264 | onmessage = function (e) { 265 | const { m, cv, data } = e.data; 266 | 267 | { 268 | // wait until worker can get a lock on 'm' 269 | using lck = m.lock(); 270 | 271 | // release the lock on 'm' and wait until main() sends data 272 | cv.wait(m, () => data.ready); 273 | 274 | // after waiting we once again own the lock on 'm' 275 | console.log("worker thread is processing data"); 276 | 277 | // send data back to main 278 | data.processed = true; 279 | console.log("worker thread is done"); 280 | 281 | } // unlocks 'm' 282 | } 283 | ``` 284 | 285 | # Prior Art 286 | 287 | 288 | 289 | - C#: 290 | - [`using` statement](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/using-statement) 291 | - [`using` declaration](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/using#using-declaration) 292 | - Java: [`try`-with-resources statement](https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html) 293 | - Python: [`with` statement](https://docs.python.org/3/reference/compound_stmts.html#the-with-statement) 294 | 295 | # Definitions 296 | 297 | - _Resource_ — An object with a specific lifetime, at the end of which either a lifetime-sensitive operation 298 | should be performed or a non-garbage-collected reference (such as a file handle, socket, etc.) should be closed or 299 | freed. 300 | - _Resource Management_ — A process whereby "resources" are released, triggering any lifetime-sensitive operations 301 | or freeing any related non-garbage-collected references. 302 | - _Implicit Resource Management_ — Indicates a system whereby the lifetime of a "resource" is managed implicitly 303 | by the runtime as part of garbage collection, such as: 304 | - `WeakMap` keys 305 | - `WeakSet` values 306 | - `WeakRef` values 307 | - `FinalizationRegistry` entries 308 | - _Explicit Resource Management_ — Indicates a system whereby the lifetime of a "resource" is managed explicitly 309 | by the user either **imperatively** (by directly calling a method like `Symbol.dispose`) or **declaratively** (through 310 | a block-scoped declaration like `using`). 311 | 312 | # Syntax 313 | 314 | ## `using` Declarations 315 | 316 | ```js 317 | // a synchronously-disposed, block-scoped resource 318 | using x = expr1; // resource w/ local binding 319 | using y = expr2, z = expr4; // multiple resources 320 | ``` 321 | 322 | 323 | # Grammar 324 | 325 | Please refer to the [specification text][Specification] for the most recent version of the grammar. 326 | 327 | ## `await using` Declarations 328 | 329 | ```js 330 | // an asynchronously-disposed, block-scoped resource 331 | await using x = expr1; // resource w/ local binding 332 | await using y = expr2, z = expr4; // multiple resources 333 | ``` 334 | 335 | An `await using` declaration can appear in the following contexts: 336 | - The top level of a _Module_ anywhere _VariableStatement_ is allowed, as long as it is not immediately nested inside 337 | of a _CaseClause_ or _DefaultClause_. 338 | - In the body of an async function or async generator anywhere a _VariableStatement_ is allowed, as long as it is not 339 | immediately nested inside of a _CaseClause_ or _DefaultClause_. 340 | - In the head of a `for-of` or `for-await-of` statement. 341 | 342 | ## `await using` in `for-of` and `for-await-of` Statements 343 | 344 | ```js 345 | for (await using x of y) ... 346 | 347 | for await (await using x of y) ... 348 | ``` 349 | 350 | You can use an `await using` declaration in a `for-of` or `for-await-of` statement inside of an async context to 351 | explicitly bind each iterated value as an async disposable resource. `for-await-of` does not implicitly make a non-async 352 | `using` declaration into an async `await using` declaration, as the `await` markers in `for-await-of` and `await using` 353 | are explicit indicators for distinct cases: `for await` *only* indicates async iteration, while `await using` *only* 354 | indicates async disposal. For example: 355 | 356 | ```js 357 | 358 | // sync iteration, sync disposal 359 | for (using x of y) ; // no implicit `await` at end of each iteration 360 | 361 | // sync iteration, async disposal 362 | for (await using x of y) ; // implicit `await` at end of each iteration 363 | 364 | // async iteration, sync disposal 365 | for await (using x of y) ; // implicit `await` at end of each iteration 366 | 367 | // async iteration, async disposal 368 | for await (await using x of y) ; // implicit `await` at end of each iteration 369 | ``` 370 | 371 | While there is some overlap in that the last three cases introduce some form of implicit `await` during execution, it 372 | is intended that the presence or absence of the `await` modifier in a `using` declaration is an explicit indicator as to 373 | whether we are expecting the iterated value to have an `@@asyncDispose` method. This distinction is in line with the 374 | behavior of `for-of` and `for-await-of`: 375 | 376 | ```js 377 | const iter = { [Symbol.iterator]() { return [].values(); } }; 378 | const asyncIter = { [Symbol.asyncIterator]() { return [].values(); } }; 379 | 380 | for (const x of iter) ; // ok: `iter` has @@iterator 381 | for (const x of asyncIter) ; // throws: `asyncIter` does not have @@iterator 382 | 383 | for await (const x of iter) ; // ok: `iter` has @@iterator (fallback) 384 | for await (const x of asyncIter) ; // ok: `asyncIter` has @@asyncIterator 385 | 386 | ``` 387 | 388 | `using` and `await using` have the same distinction: 389 | 390 | ```js 391 | const res = { [Symbol.dispose]() {} }; 392 | const asyncRes = { [Symbol.asyncDispose]() {} }; 393 | 394 | using x = res; // ok: `res` has @@dispose 395 | using x = asyncRes; // throws: `asyncRes` does not have @@dispose 396 | 397 | await using x = res; // ok: `res` has @@dispose (fallback) 398 | await using x = asyncres; // ok: `asyncRes` has @@asyncDispose 399 | ``` 400 | 401 | This results in a matrix of behaviors based on the presence of each `await` marker: 402 | 403 | ```js 404 | const res = { [Symbol.dispose]() {} }; 405 | const asyncRes = { [Symbol.asyncDispose]() {} }; 406 | const iter = { [Symbol.iterator]() { return [res, asyncRes].values(); } }; 407 | const asyncIter = { [Symbol.asyncIterator]() { return [res, asyncRes].values(); } }; 408 | 409 | for (using x of iter) ; 410 | // sync iteration, sync disposal 411 | // - `iter` has @@iterator: ok 412 | // - `res` has @@dispose: ok 413 | // - `asyncRes` does not have @@dispose: *error* 414 | 415 | for (using x of asyncIter) ; 416 | // sync iteration, sync disposal 417 | // - `asyncIter` does not have @@iterator: *error* 418 | 419 | for (await using x of iter) ; 420 | // sync iteration, async disposal 421 | // - `iter` has @@iterator: ok 422 | // - `res` has @@dispose (fallback): ok 423 | // - `asyncRes` has @@asyncDispose: ok 424 | 425 | for (await using x of asyncIter) ; 426 | // sync iteration, async disposal 427 | // - `asyncIter` does not have @@iterator: error 428 | 429 | for await (using x of iter) ; 430 | // async iteration, sync disposal 431 | // - `iter` has @@iterator (fallback): ok 432 | // - `res` has @@dispose: ok 433 | // - `asyncRes` does not have @@dispose: error 434 | 435 | for await (using x of asyncIter) ; 436 | // async iteration, sync disposal 437 | // - `asyncIter` has @@asyncIterator: ok 438 | // - `res` has @@dispose: ok 439 | // - `asyncRes` does not have @@dispose: error 440 | 441 | for await (await using x of iter) ; 442 | // async iteration, async disposal 443 | // - `iter` has @@iterator (fallback): ok 444 | // - `res` has @@dispose (fallback): ok 445 | // - `asyncRes` does has @@asyncDispose: ok 446 | 447 | for await (await using x of asyncIter) ; 448 | // async iteration, async disposal 449 | // - `asyncIter` has @@asyncIterator: ok 450 | // - `res` has @@dispose (fallback): ok 451 | // - `asyncRes` does has @@asyncDispose: ok 452 | ``` 453 | 454 | Or, in table form: 455 | 456 | | Syntax | Iteration | Disposal | 457 | |:---------------------------------|:------------------------------:|:----------------------------:| 458 | | `for (using x of y)` | `@@iterator` | `@@dispose` | 459 | | `for (await using x of y)` | `@@iterator` | `@@asyncDispose`/`@@dispose` | 460 | | `for await (using x of y)` | `@@asyncIterator`/`@@iterator` | `@@dispose` | 461 | | `for await (await using x of y)` | `@@asyncIterator`/`@@iterator` | `@@asyncDispose`/`@@dispose` | 462 | 463 | # Semantics 464 | 465 | ## `using` Declarations 466 | 467 | ### `using` Declarations with Explicit Local Bindings 468 | 469 | ```grammarkdown 470 | UsingDeclaration : 471 | `using` BindingList `;` 472 | 473 | LexicalBinding : 474 | BindingIdentifier Initializer 475 | ``` 476 | 477 | When a `using` declaration is parsed with _BindingIdentifier_ _Initializer_, the bindings created in the declaration 478 | are tracked for disposal at the end of the containing _Block_ or _Module_ (a `using` declaration cannot be used 479 | at the top level of a _Script_): 480 | 481 | ```js 482 | { 483 | ... // (1) 484 | using x = expr1; 485 | ... // (2) 486 | } 487 | ``` 488 | 489 | The above example has similar runtime semantics as the following transposed representation: 490 | 491 | ```js 492 | { 493 | const $$try = { stack: [], error: undefined, hasError: false }; 494 | try { 495 | ... // (1) 496 | 497 | const x = expr1; 498 | if (x !== null && x !== undefined) { 499 | const $$dispose = x[Symbol.dispose]; 500 | if (typeof $$dispose !== "function") { 501 | throw new TypeError(); 502 | } 503 | $$try.stack.push({ value: x, dispose: $$dispose }); 504 | } 505 | 506 | ... // (2) 507 | } 508 | catch ($$error) { 509 | $$try.error = $$error; 510 | $$try.hasError = true; 511 | } 512 | finally { 513 | while ($$try.stack.length) { 514 | const { value: $$expr, dispose: $$dispose } = $$try.stack.pop(); 515 | try { 516 | $$dispose.call($$expr); 517 | } 518 | catch ($$error) { 519 | $$try.error = $$try.hasError ? new SuppressedError($$error, $$try.error) : $$error; 520 | $$try.hasError = true; 521 | } 522 | } 523 | if ($$try.hasError) { 524 | throw $$try.error; 525 | } 526 | } 527 | } 528 | ``` 529 | 530 | If exceptions are thrown both in the block following the `using` declaration and in the call to 531 | `[Symbol.dispose]()`, all exceptions are reported. 532 | 533 | ### `using` Declarations with Multiple Resources 534 | 535 | A `using` declaration can mix multiple explicit bindings in the same declaration: 536 | 537 | ```js 538 | { 539 | ... 540 | using x = expr1, y = expr2; 541 | ... 542 | } 543 | ``` 544 | 545 | These bindings are again used to perform resource disposal when the _Block_ or _Module_ exits, however in this case 546 | `[Symbol.dispose]()` is invoked in the reverse order of their declaration. This is _approximately_ equivalent to the 547 | following: 548 | 549 | ```js 550 | { 551 | ... // (1) 552 | using x = expr1; 553 | using y = expr2; 554 | ... // (2) 555 | } 556 | ``` 557 | 558 | Both of the above cases would have similar runtime semantics as the following transposed representation: 559 | 560 | ```js 561 | { 562 | const $$try = { stack: [], error: undefined, hasError: false }; 563 | try { 564 | ... // (1) 565 | 566 | const x = expr1; 567 | if (x !== null && x !== undefined) { 568 | const $$dispose = x[Symbol.dispose]; 569 | if (typeof $$dispose !== "function") { 570 | throw new TypeError(); 571 | } 572 | $$try.stack.push({ value: x, dispose: $$dispose }); 573 | } 574 | 575 | const y = expr2; 576 | if (y !== null && y !== undefined) { 577 | const $$dispose = y[Symbol.dispose]; 578 | if (typeof $$dispose !== "function") { 579 | throw new TypeError(); 580 | } 581 | $$try.stack.push({ value: y, dispose: $$dispose }); 582 | } 583 | 584 | ... // (2) 585 | } 586 | catch ($$error) { 587 | $$try.error = $$error; 588 | $$try.hasError = true; 589 | } 590 | finally { 591 | while ($$try.stack.length) { 592 | const { value: $$expr, dispose: $$dispose } = $$try.stack.pop(); 593 | try { 594 | $$dispose.call($$expr); 595 | } 596 | catch ($$error) { 597 | $$try.error = $$try.hasError ? new SuppressedError($$error, $$try.error) : $$error; 598 | $$try.hasError = true; 599 | } 600 | } 601 | if ($$try.hasError) { 602 | throw $$try.error; 603 | } 604 | } 605 | } 606 | ``` 607 | 608 | Since we must always ensure that we properly release resources, we must ensure that any abrupt completion that might 609 | occur during binding initialization results in evaluation of the cleanup step. When there are multiple declarations in 610 | the list, we track each resource in the order they are declared. As a result, we must release these resources in reverse 611 | order. 612 | 613 | ### `using` Declarations and `null` or `undefined` Values 614 | 615 | This proposal has opted to ignore `null` and `undefined` values provided to the `using` declarations. This is similar to 616 | the behavior of `using` in C#, which also allows `null`. One primary reason for this behavior is to simplify a common 617 | case where a resource might be optional, without requiring duplication of work or needless allocations: 618 | 619 | ```js 620 | if (isResourceAvailable()) { 621 | using resource = getResource(); 622 | ... // (1) 623 | resource.doSomething() 624 | ... // (2) 625 | } 626 | else { 627 | // duplicate code path above 628 | ... // (1) above 629 | ... // (2) above 630 | } 631 | ``` 632 | 633 | Compared to: 634 | 635 | ```js 636 | using resource = isResourceAvailable() ? getResource() : undefined; 637 | ... // (1) do some work with or without resource 638 | resource?.doSomething(); 639 | ... // (2) do some other work with or without resource 640 | ``` 641 | 642 | ### `using` Declarations and Values Without `[Symbol.dispose]` 643 | 644 | If a resource does not have a callable `[Symbol.dispose]` member, a `TypeError` would be thrown **immediately** when the 645 | resource is tracked. 646 | 647 | ### `using` Declarations in `for-of` and `for-await-of` Loops 648 | 649 | A `using` declaration _may_ occur in the _ForDeclaration_ of a `for-of` or `for-await-of` loop: 650 | 651 | ```js 652 | for (using x of iterateResources()) { 653 | // use x 654 | } 655 | ``` 656 | 657 | In this case, the value bound to `x` in each iteration will be _synchronously_ disposed at the end of each iteration. 658 | This will not dispose resources that are not iterated, such as if iteration is terminated early due to `return`, 659 | `break`, or `throw`. 660 | 661 | `using` declarations _may not_ be used in in the head of a `for-in` loop. 662 | 663 | ## `await using` Declarations 664 | 665 | ### `await using` Declarations with Explicit Local Bindings 666 | 667 | ```grammarkdown 668 | UsingDeclaration : 669 | `await` `using` BindingList `;` 670 | 671 | LexicalBinding : 672 | BindingIdentifier Initializer 673 | ``` 674 | 675 | When an `await using` declaration is parsed with _BindingIdentifier_ _Initializer_, the bindings created in the 676 | declaration are tracked for disposal at the end of the containing async function body, _Block_, or _Module_: 677 | 678 | ```js 679 | { 680 | ... // (1) 681 | await using x = expr1; 682 | ... // (2) 683 | } 684 | ``` 685 | 686 | The above example has similar runtime semantics as the following transposed representation: 687 | 688 | ```js 689 | { 690 | const $$try = { stack: [], error: undefined, hasError: false }; 691 | try { 692 | ... // (1) 693 | 694 | const x = expr1; 695 | if (x !== null && x !== undefined) { 696 | let $$dispose = x[Symbol.asyncDispose]; 697 | if (typeof $$dispose !== "function") { 698 | $$dispose = x[Symbol.dispose]; 699 | } 700 | if (typeof $$dispose !== "function") { 701 | throw new TypeError(); 702 | } 703 | $$try.stack.push({ value: x, dispose: $$dispose }); 704 | } 705 | 706 | ... // (2) 707 | } 708 | catch ($$error) { 709 | $$try.error = $$error; 710 | $$try.hasError = true; 711 | } 712 | finally { 713 | while ($$try.stack.length) { 714 | const { value: $$expr, dispose: $$dispose } = $$try.stack.pop(); 715 | try { 716 | await $$dispose.call($$expr); 717 | } 718 | catch ($$error) { 719 | $$try.error = $$try.hasError ? new SuppressedError($$error, $$try.error) : $$error; 720 | $$try.hasError = true; 721 | } 722 | } 723 | if ($$try.hasError) { 724 | throw $$try.error; 725 | } 726 | } 727 | } 728 | ``` 729 | 730 | If exceptions are thrown both in the statements following the `await using` declaration and in the call to 731 | `[Symbol.asyncDispose]()`, all exceptions are reported. 732 | 733 | ### `await using` Declarations with Multiple Resources 734 | 735 | An `await using` declaration can mix multiple explicit bindings in the same declaration: 736 | 737 | ```js 738 | { 739 | ... 740 | await using x = expr1, y = expr2; 741 | ... 742 | } 743 | ``` 744 | 745 | These bindings are again used to perform resource disposal when the _Block_ or _Module_ exits, however in this case each 746 | resource's `[Symbol.asyncDispose]()` is invoked in the reverse order of their declaration. This is _approximately_ 747 | equivalent to the following: 748 | 749 | ```js 750 | { 751 | ... // (1) 752 | await using x = expr1; 753 | await using y = expr2; 754 | ... // (2) 755 | } 756 | ``` 757 | 758 | Both of the above cases would have similar runtime semantics as the following transposed representation: 759 | 760 | ```js 761 | { 762 | const $$try = { stack: [], error: undefined, hasError: false }; 763 | try { 764 | ... // (1) 765 | 766 | const x = expr1; 767 | if (x !== null && x !== undefined) { 768 | let $$dispose = x[Symbol.asyncDispose]; 769 | if (typeof $$dispose !== "function") { 770 | $$dispose = x[Symbol.dispose]; 771 | } 772 | if (typeof $$dispose !== "function") { 773 | throw new TypeError(); 774 | } 775 | $$try.stack.push({ value: x, dispose: $$dispose }); 776 | } 777 | 778 | const y = expr2; 779 | if (y !== null && y !== undefined) { 780 | let $$dispose = y[Symbol.asyncDispose]; 781 | if (typeof $$dispose !== "function") { 782 | $$dispose = y[Symbol.dispose]; 783 | } 784 | if (typeof $$dispose !== "function") { 785 | throw new TypeError(); 786 | } 787 | $$try.stack.push({ value: y, dispose: $$dispose }); 788 | } 789 | 790 | ... // (2) 791 | } 792 | catch ($$error) { 793 | $$try.error = $$error; 794 | $$try.hasError = true; 795 | } 796 | finally { 797 | while ($$try.stack.length) { 798 | const { value: $$expr, dispose: $$dispose } = $$try.stack.pop(); 799 | try { 800 | await $$dispose.call($$expr); 801 | } 802 | catch ($$error) { 803 | $$try.error = $$try.hasError ? new SuppressedError($$error, $$try.error) : $$error; 804 | $$try.hasError = true; 805 | } 806 | } 807 | if ($$try.hasError) { 808 | throw $$try.error; 809 | } 810 | } 811 | } 812 | ``` 813 | 814 | Since we must always ensure that we properly release resources, we must ensure that any abrupt completion that might 815 | occur during binding initialization results in evaluation of the cleanup step. When there are multiple declarations in 816 | the list, we track each resource in the order they are declared. As a result, we must release these resources in reverse 817 | order. 818 | 819 | ### `await using` Declarations and `null` or `undefined` Values 820 | 821 | This proposal has opted to ignore `null` and `undefined` values provided to `await using` declarations. This is 822 | consistent with the proposed behavior for the `using` declarations in this proposal. Like in the sync case, this allows 823 | simplifying a common case where a resource might be optional, without requiring duplication of work or needless 824 | allocations: 825 | 826 | ```js 827 | if (isResourceAvailable()) { 828 | await using resource = getResource(); 829 | ... // (1) 830 | resource.doSomething() 831 | ... // (2) 832 | } 833 | else { 834 | // duplicate code path above 835 | ... // (1) above 836 | ... // (2) above 837 | } 838 | ``` 839 | 840 | Compared to: 841 | 842 | ```js 843 | await using resource = isResourceAvailable() ? getResource() : undefined; 844 | ... // (1) do some work with or without resource 845 | resource?.doSomething(); 846 | ... // (2) do some other work with or without resource 847 | ``` 848 | 849 | ### `await using` Declarations and Values Without `[Symbol.asyncDispose]` or `[Symbol.dispose]` 850 | 851 | 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. 852 | 853 | ### `await using` Declarations in `for-of` and `for-await-of` Loops 854 | 855 | An `await using` declaration _may_ occur in the _ForDeclaration_ of a `for-await-of` loop: 856 | 857 | ```js 858 | for await (await using x of iterateResources()) { 859 | // use x 860 | } 861 | ``` 862 | 863 | In this case, the value bound to `x` in each iteration will be _asynchronously_ disposed at the end of each iteration. 864 | This will not dispose resources that are not iterated, such as if iteration is terminated early due to `return`, 865 | `break`, or `throw`. 866 | 867 | `await using` declarations _may not_ be used in in the head of a `for-of` or `for-in` loop. 868 | 869 | ### Implicit Async Interleaving Points ("implicit `await`") 870 | 871 | The `await using` syntax introduces an implicit async interleaving point (i.e., an implicit `await`) whenever control 872 | flow exits an async function body, _Block_, or _Module_ containing an `await using` declaration. This means that two 873 | statements that currently execute in the same microtask, such as: 874 | 875 | ```js 876 | async function f() { 877 | { 878 | a(); 879 | } // exit block 880 | b(); // same microtask as call to `a()` 881 | } 882 | ``` 883 | 884 | will instead execute in different microtasks if an `await using` declaration is introduced: 885 | 886 | ```js 887 | async function f() { 888 | { 889 | await using x = ...; 890 | a(); 891 | } // exit block, implicit `await` 892 | b(); // different microtask from call to `a()`. 893 | } 894 | ``` 895 | 896 | It is important that such an implicit interleaving point be adequately indicated within the syntax. We believe that 897 | the presence of `await using` within such a block is an adequate indicator, since it should be fairly easy to recognize 898 | a _Block_ containing an `await using` statement in well-formatted code. 899 | 900 | It is also feasible for editors to use features such as syntax highlighting, editor decorations, and inlay hints to 901 | further highlight such transitions, without needing to specify additional syntax. 902 | 903 | Further discussion around the `await using` syntax and how it pertains to implicit async interleaving points can be 904 | found in [#1](https://github.com/tc39/proposal-async-explicit-resource-management/issues/1). 905 | 906 | # Examples 907 | 908 | The following show examples of using this proposal with various APIs, assuming those APIs adopted this proposal. 909 | 910 | ### WHATWG Streams API 911 | ```js 912 | { 913 | using reader = stream.getReader(); 914 | const { value, done } = reader.read(); 915 | } // 'reader' is disposed 916 | ``` 917 | 918 | ### NodeJS FileHandle 919 | ```js 920 | { 921 | using f1 = await fs.promises.open(s1, constants.O_RDONLY), 922 | f2 = await fs.promises.open(s2, constants.O_WRONLY); 923 | const buffer = Buffer.alloc(4092); 924 | const { bytesRead } = await f1.read(buffer); 925 | await f2.write(buffer, 0, bytesRead); 926 | } // 'f2' is disposed, then 'f1' is disposed 927 | ``` 928 | 929 | ### NodeJS Streams 930 | ```js 931 | { 932 | await using writable = ...; 933 | writable.write(...); 934 | } // 'writable.end()' is called and its result is awaited 935 | ``` 936 | 937 | ### Logging and tracing 938 | ```js 939 | // audit privileged function call entry and exit 940 | function privilegedActivity() { 941 | using activity = auditLog.startActivity("privilegedActivity"); // log activity start 942 | ... 943 | } // log activity end 944 | ``` 945 | 946 | ### Async Coordination 947 | ```js 948 | import { Semaphore } from "..."; 949 | const sem = new Semaphore(1); // allow one participant at a time 950 | 951 | export async function tryUpdate(record) { 952 | using lck = await sem.wait(); // asynchronously block until we are the sole participant 953 | ... 954 | } // synchronously release semaphore and notify the next participant 955 | ``` 956 | 957 | ### Three-Phase Commit Transactions 958 | ```js 959 | // roll back transaction if either action fails 960 | async function transfer(account1, account2) { 961 | await using tx = transactionManager.startTransaction(account1, account2); 962 | await account1.debit(amount); 963 | await account2.credit(amount); 964 | 965 | // mark transaction success if we reach this point 966 | tx.succeeded = true; 967 | } // await transaction commit or rollback 968 | ``` 969 | 970 | ### Shared Structs 971 | **main_thread.js** 972 | ```js 973 | // main_thread.js 974 | shared struct Data { 975 | mut; 976 | cv; 977 | ready = 0; 978 | processed = 0; 979 | // ... 980 | } 981 | 982 | const data = Data(); 983 | data.mut = Atomics.Mutex(); 984 | data.cv = Atomics.ConditionVariable(); 985 | 986 | // start two workers 987 | startWorker1(data); 988 | startWorker2(data); 989 | ``` 990 | 991 | **worker1.js** 992 | ```js 993 | const data = ...; 994 | const { mut, cv } = data; 995 | 996 | { 997 | // lock mutex 998 | using lck = Atomics.Mutex.lock(mut); 999 | 1000 | // NOTE: at this point we currently own the lock 1001 | 1002 | // load content into data and signal we're ready 1003 | // ... 1004 | Atomics.store(data, "ready", 1); 1005 | 1006 | } // release mutex 1007 | 1008 | // NOTE: at this point we no longer own the lock 1009 | 1010 | // notify worker 2 that it should wake 1011 | Atomics.ConditionVariable.notifyOne(cv); 1012 | 1013 | { 1014 | // reacquire lock on mutex 1015 | using lck = Atomics.Mutex.lock(mut); 1016 | 1017 | // NOTE: at this point we currently own the lock 1018 | 1019 | // release mutex and wait until condition is met to reacquire it 1020 | Atomics.ConditionVariable.wait(mut, () => Atomics.load(data, "processed") === 1); 1021 | 1022 | // NOTE: at this point we currently own the lock 1023 | 1024 | // Do something with the processed data 1025 | // ... 1026 | 1027 | } // release mutex 1028 | 1029 | // NOTE: at this point we no longer own the lock 1030 | ``` 1031 | 1032 | **worker2.js** 1033 | ```js 1034 | const data = ...; 1035 | const { mut, cv } = data; 1036 | 1037 | { 1038 | // lock mutex 1039 | using lck = Atomics.Mutex.lock(mut); 1040 | 1041 | // NOTE: at this point we currently own the lock 1042 | 1043 | // release mutex and wait until condition is met to reacquire it 1044 | Atomics.ConditionVariable.wait(mut, () => Atomics.load(data, "ready") === 1); 1045 | 1046 | // NOTE: at this point we currently own the lock 1047 | 1048 | // read in values from data, perform our processing, then indicate we are done 1049 | // ... 1050 | Atomics.store(data, "processed", 1); 1051 | 1052 | } // release mutex 1053 | 1054 | // NOTE: at this point we no longer own the lock 1055 | ``` 1056 | 1057 | # API 1058 | 1059 | ## Additions to `Symbol` 1060 | 1061 | This proposal adds the `dispose` and `asyncDispose` properties to the `Symbol` constructor, whose values are the 1062 | `@@dispose` and `@@asyncDispose` internal symbols: 1063 | 1064 | **Well-known Symbols** 1065 | | Specification Name | \[\[Description]] | Value and Purpose | 1066 | |:-|:-|:-| 1067 | | _@@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. | 1068 | | _@@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. | 1069 | 1070 | **TypeScript Definition** 1071 | ```ts 1072 | interface SymbolConstructor { 1073 | readonly asyncDispose: unique symbol; 1074 | readonly dispose: unique symbol; 1075 | } 1076 | ``` 1077 | 1078 | ## The `SuppressedError` Error 1079 | 1080 | If an exception occurs during resource disposal, it is possible that it might suppress an existing exception thrown 1081 | from the body, or from the disposal of another resource. Languages like Java allow you to access a suppressed exception 1082 | via a [`getSuppressed()`](https://docs.oracle.com/javase/7/docs/api/java/lang/Throwable.html#getSuppressed()) method on 1083 | the exception. However, ECMAScript allows you to throw any value, not just `Error`, so there is no convenient place to 1084 | attach a suppressed exception. To better surface these suppressed exceptions and support both logging and error 1085 | recovery, this proposal seeks to introduce a new `SuppressedError` built-in `Error` subclass which would contain both 1086 | the error that was most recently thrown, as well as the error that was suppressed: 1087 | 1088 | ```js 1089 | class SuppressedError extends Error { 1090 | /** 1091 | * Wraps an error that suppresses another error, and the error that was suppressed. 1092 | * @param {*} error The error that resulted in a suppression. 1093 | * @param {*} suppressed The error that was suppressed. 1094 | * @param {string} message The message for the error. 1095 | * @param {{ cause?: * }} [options] Options for the error. 1096 | */ 1097 | constructor(error, suppressed, message, options); 1098 | 1099 | /** 1100 | * The name of the error (i.e., `"SuppressedError"`). 1101 | * @type {string} 1102 | */ 1103 | name = "SuppressedError"; 1104 | 1105 | /** 1106 | * The error that resulted in a suppression. 1107 | * @type {*} 1108 | */ 1109 | error; 1110 | 1111 | /** 1112 | * The error that was suppressed. 1113 | * @type {*} 1114 | */ 1115 | suppressed; 1116 | 1117 | /** 1118 | * The message for the error. 1119 | * @type {*} 1120 | */ 1121 | message; 1122 | } 1123 | ``` 1124 | 1125 | We've chosen to use `SuppressedError` over `AggregateError` for several reasons: 1126 | - `AggregateError` is designed to hold a list of multiple errors, with no correlation between those errors, while 1127 | `SuppressedError` is intended to hold references to two errors with a direct correlation. 1128 | - `AggregateError` is intended to ideally hold a flat list of errors. `SuppressedError` is intended to hold a jagged set 1129 | of errors (i.e., `e.suppressed.suppressed.suppressed` if there were successive error suppressions). 1130 | - The only error correlation on `AggregateError` is through `cause`, however a `SuppressedError` isn't "caused" by the 1131 | error it suppresses. In addition, `cause` is intended to be optional, while the `error` of a `SuppressedError` must 1132 | always be defined. 1133 | 1134 | ## Built-in Disposables 1135 | 1136 | ### `%IteratorPrototype%.@@dispose()` 1137 | 1138 | We also propose to add `Symbol.dispose` to the built-in `%IteratorPrototype%` as if it had the following behavior: 1139 | 1140 | ```js 1141 | %IteratorPrototype%[Symbol.dispose] = function () { 1142 | this.return(); 1143 | } 1144 | ``` 1145 | 1146 | ### `%AsyncIteratorPrototype%.@@asyncDispose()` 1147 | 1148 | We propose to add `Symbol.asyncDispose` to the built-in `%AsyncIteratorPrototype%` as if it had the following behavior: 1149 | 1150 | ```js 1151 | %AsyncIteratorPrototype%[Symbol.asyncDispose] = async function () { 1152 | await this.return(); 1153 | } 1154 | ``` 1155 | 1156 | ### Other Possibilities 1157 | 1158 | We could also consider adding `Symbol.dispose` to such objects as the return value from `Proxy.revocable()`, but that 1159 | is currently out of scope for the current proposal. 1160 | 1161 | ## The Common `Disposable` and `AsyncDisposable` Interfaces 1162 | 1163 | ### The `Disposable` Interface 1164 | 1165 | An object is _disposable_ if it conforms to the following interface: 1166 | 1167 | | Property | Value | Requirements | 1168 | |:-|:-|:-| 1169 | | `@@dispose` | A function that performs explicit cleanup. | The function should return `undefined`. | 1170 | 1171 | **TypeScript Definition** 1172 | ```ts 1173 | interface Disposable { 1174 | /** 1175 | * Disposes of resources within this object. 1176 | */ 1177 | [Symbol.dispose](): void; 1178 | } 1179 | ``` 1180 | 1181 | ### The `AsyncDisposable` Interface 1182 | 1183 | An object is _async disposable_ if it conforms to the following interface: 1184 | 1185 | | Property | Value | Requirements | 1186 | |:-|:-|:-| 1187 | | `@@asyncDispose` | An async function that performs explicit cleanup. | The function should return a `Promise`. | 1188 | 1189 | **TypeScript Definition** 1190 | ```ts 1191 | interface AsyncDisposable { 1192 | /** 1193 | * Disposes of resources within this object. 1194 | */ 1195 | [Symbol.asyncDispose](): Promise; 1196 | } 1197 | ``` 1198 | 1199 | ## The `DisposableStack` and `AsyncDisposableStack` container objects 1200 | 1201 | This proposal adds two global objects that can act as containers to aggregate disposables, guaranteeing that every 1202 | disposable resource in the container is disposed when the respective disposal method is called. If any disposable in the 1203 | container throws an error during dispose, it would be thrown at the end (possibly wrapped in a `SuppressedError` if 1204 | multiple errors were thrown): 1205 | 1206 | ```js 1207 | class DisposableStack { 1208 | constructor(); 1209 | 1210 | /** 1211 | * Gets a value indicating whether the stack has been disposed. 1212 | * @returns {boolean} 1213 | */ 1214 | get disposed(); 1215 | 1216 | /** 1217 | * Alias for `[Symbol.dispose]()`. 1218 | */ 1219 | dispose(); 1220 | 1221 | /** 1222 | * Adds a resource to the top of the stack. Has no effect if provided `null` or `undefined`. 1223 | * @template {Disposable | null | undefined} T 1224 | * @param {T} value - A `Disposable` object, `null`, or `undefined`. 1225 | * @returns {T} The provided value. 1226 | */ 1227 | use(value); 1228 | 1229 | /** 1230 | * Adds a non-disposable resource and a disposal callback to the top of the stack. 1231 | * @template T 1232 | * @param {T} value - A resource to be disposed. 1233 | * @param {(value: T) => void} onDispose - A callback invoked to dispose the provided value. 1234 | * @returns {T} The provided value. 1235 | */ 1236 | adopt(value, onDispose); 1237 | 1238 | /** 1239 | * Adds a disposal callback to the top of the stack. 1240 | * @param {() => void} onDispose - A callback to evaluate when this object is disposed. 1241 | * @returns {void} 1242 | */ 1243 | defer(onDispose); 1244 | 1245 | /** 1246 | * Moves all resources currently in this stack into a new `DisposableStack`. 1247 | * @returns {DisposableStack} The new `DisposableStack`. 1248 | */ 1249 | move(); 1250 | 1251 | /** 1252 | * Disposes of resources within this object. 1253 | * @returns {void} 1254 | */ 1255 | [Symbol.dispose](); 1256 | 1257 | [Symbol.toStringTag]; 1258 | } 1259 | ``` 1260 | 1261 | `AsyncDisposableStack` is the async version of `DisposableStack` and is a container used to aggregate async disposables, 1262 | guaranteeing that every disposable resource in the container is disposed when the respective disposal method is called. 1263 | If any disposable in the container throws an error during dispose, or results in a rejected `Promise`, it would be 1264 | thrown at the end (possibly wrapped in a `SuppressedError` if multiple errors were thrown): 1265 | 1266 | These classes provided the following capabilities: 1267 | - Aggregation 1268 | - Interoperation and customization 1269 | - Assist in complex construction 1270 | 1271 | > **NOTE:** `DisposableStack` is inspired by Python's 1272 | > [`ExitStack`](https://docs.python.org/3/library/contextlib.html#contextlib.ExitStack). 1273 | 1274 | > **NOTE:** `AsyncDisposableStack` is inspired by Python's 1275 | > [`AsyncExitStack`](https://docs.python.org/3/library/contextlib.html#contextlib.AsyncExitStack). 1276 | 1277 | ### Aggregation 1278 | 1279 | The `DisposableStack` and `AsyncDisposableStack` classes provid the ability to aggregate multiple disposable resources 1280 | into a single container. When the `DisposableStack` container is disposed, each object in the container is also 1281 | guaranteed to be disposed (barring early termination of the program). If any resource throws an error during dispose, 1282 | it will be collected and rethrown after all resources are disposed. If there were multiple errors, they will be wrapped 1283 | in nested `SuppressedError` objects. 1284 | 1285 | For example: 1286 | 1287 | ```js 1288 | // sync 1289 | const stack = new DisposableStack(); 1290 | const resource1 = stack.use(getResource1()); 1291 | const resource2 = stack.use(getResource2()); 1292 | const resource3 = stack.use(getResource3()); 1293 | stack[Symbol.dispose](); // disposes of resource3, then resource2, then resource1 1294 | ``` 1295 | 1296 | ```js 1297 | // async 1298 | const stack = new AsyncDisposableStack(); 1299 | const resource1 = stack.use(getResource1()); 1300 | const resource2 = stack.use(getResource2()); 1301 | const resource3 = stack.use(getResource3()); 1302 | await stack[Symbol.asyncDispose](); // dispose and await disposal result of resource3, then resource2, then resource1 1303 | ``` 1304 | 1305 | If all of `resource1`, `resource2` and `resource3` were to throw during disposal, this would produce an exception 1306 | similar to the following: 1307 | 1308 | ```js 1309 | new SuppressedError( 1310 | /*error*/ exception_from_resource3_disposal, 1311 | /*suppressed*/ new SuppressedError( 1312 | /*error*/ exception_from_resource2_disposal, 1313 | /*suppressed*/ exception_from_resource1_disposal 1314 | ) 1315 | ) 1316 | ``` 1317 | 1318 | ### Interoperation and Customization 1319 | 1320 | The `DisposableStack` and `AsyncDisposableStack` classes also provide the ability to create a disposable resource from a 1321 | simple callback. This callback will be executed when the stack's disposal method is executed. 1322 | 1323 | The ability to create a disposable resource from a callback has several benefits: 1324 | 1325 | - It allows developers to leverage `using`/`await using` while working with existing resources that do not conform to the 1326 | `Symbol.dispose`/`Symbol.asyncDispose` mechanic: 1327 | ```js 1328 | { 1329 | using stack = new DisposableStack(); 1330 | const reader = stack.adopt(createReader(), reader => reader.releaseLock()); 1331 | ... 1332 | } 1333 | ``` 1334 | - It grants user the ability to schedule other cleanup work to evaluate at the end of the block similar to Go's 1335 | `defer` statement: 1336 | ```js 1337 | function f() { 1338 | using stack = new DisposableStack(); 1339 | console.log("enter"); 1340 | stack.defer(() => console.log("exit")); 1341 | ... 1342 | } 1343 | ``` 1344 | 1345 | ### Assist in Complex Construction 1346 | 1347 | A user-defined disposable class might need to allocate and track multiple nested resources that should be disposed when 1348 | the class instance is disposed. However, properly managing the lifetime of these nested resources in the class 1349 | constructor can sometimes be difficult. The `move` method of `DisposableStack`/`AsyncDisposableStack` helps to more 1350 | easily manage lifetime in these scenarios: 1351 | 1352 | ```js 1353 | // sync 1354 | class PluginHost { 1355 | #disposed = false; 1356 | #disposables; 1357 | #channel; 1358 | #socket; 1359 | 1360 | constructor() { 1361 | // Create a DisposableStack that is disposed when the constructor exits. 1362 | // If construction succeeds, we move everything out of `stack` and into 1363 | // `#disposables` to be disposed later. 1364 | using stack = new DisposableStack(); 1365 | 1366 | // Create an IPC adapter around process.send/process.on("message"). 1367 | // When disposed, it unsubscribes from process.on("message"). 1368 | this.#channel = stack.use(new NodeProcessIpcChannelAdapter(process)); 1369 | 1370 | // Create a pseudo-websocket that sends and receives messages over 1371 | // a NodeJS IPC channel. 1372 | this.#socket = stack.use(new NodePluginHostIpcSocket(this.#channel)); 1373 | 1374 | // If we made it here, then there were no errors during construction and 1375 | // we can safely move the disposables out of `stack` and into `#disposables`. 1376 | this.#disposables = stack.move(); 1377 | 1378 | // If construction failed, then `stack` would be disposed before reaching 1379 | // the line above. Event handlers would be removed, allowing `#channel` and 1380 | // `#socket` to be GC'd. 1381 | } 1382 | 1383 | loadPlugin(file) { 1384 | // A disposable should try to ensure access is consistent with its "disposed" state, though this isn't strictly 1385 | // necessary since some disposables could be reusable (i.e., a Connection with an `open()` method, etc.). 1386 | if (this.#disposed) throw new ReferenceError("Object is disposed."); 1387 | // ... 1388 | } 1389 | 1390 | [Symbol.dispose]() { 1391 | if (!this.#disposed) { 1392 | this.#disposed = true; 1393 | const disposables = this.#disposables; 1394 | 1395 | // NOTE: we can free `#socket` and `#channel` here since they will be disposed by the call to 1396 | // `disposables[Symbol.dispose]()`, below. This isn't strictly a requirement for every Disposable, but is 1397 | // good housekeeping since these objects will no longer be useable. 1398 | this.#socket = undefined; 1399 | this.#channel = undefined; 1400 | this.#disposables = undefined; 1401 | 1402 | // Dispose all resources in `disposables` 1403 | disposables[Symbol.dispose](); 1404 | } 1405 | } 1406 | } 1407 | ``` 1408 | 1409 | ```js 1410 | // async 1411 | const privateConstructorSentinel = {}; 1412 | 1413 | class AsyncPluginHost { 1414 | #disposed = false; 1415 | #disposables; 1416 | #channel; 1417 | #socket; 1418 | 1419 | /** @private */ 1420 | constructor(arg) { 1421 | if (arg !== privateConstructorSentinel) throw new TypeError("Use AsyncPluginHost.create() instead"); 1422 | } 1423 | 1424 | // NOTE: there's no such thing as an async constructor 1425 | static async create() { 1426 | const host = new AsyncPluginHost(privateConstructorSentinel); 1427 | 1428 | // Create an AsyncDisposableStack that is disposed when the constructor exits. 1429 | // If construction succeeds, we move everything out of `stack` and into 1430 | // `#disposables` to be disposed later. 1431 | await using stack = new AsyncDisposableStack(); 1432 | 1433 | 1434 | // Create an IPC adapter around process.send/process.on("message"). 1435 | // When disposed, it unsubscribes from process.on("message"). 1436 | host.#channel = stack.use(new NodeProcessIpcChannelAdapter(process)); 1437 | 1438 | // Create a pseudo-websocket that sends and receives messages over 1439 | // a NodeJS IPC channel. 1440 | host.#socket = stack.use(new NodePluginHostIpcSocket(host.#channel)); 1441 | 1442 | // If we made it here, then there were no errors during construction and 1443 | // we can safely move the disposables out of `stack` and into `#disposables`. 1444 | host.#disposables = stack.move(); 1445 | 1446 | // If construction failed, then `stack` would be asynchronously disposed before reaching 1447 | // the line above. Event handlers would be removed, allowing `#channel` and 1448 | // `#socket` to be GC'd. 1449 | return host; 1450 | } 1451 | 1452 | loadPlugin(file) { 1453 | // A disposable should try to ensure access is consistent with its "disposed" state, though this isn't strictly 1454 | // necessary since some disposables could be reusable (i.e., a Connection with an `open()` method, etc.). 1455 | if (this.#disposed) throw new ReferenceError("Object is disposed."); 1456 | // ... 1457 | } 1458 | 1459 | async [Symbol.asyncDispose]() { 1460 | if (!this.#disposed) { 1461 | this.#disposed = true; 1462 | const disposables = this.#disposables; 1463 | 1464 | // NOTE: we can free `#socket` and `#channel` here since they will be disposed by the call to 1465 | // `disposables[Symbol.asyncDispose]()`, below. This isn't strictly a requirement for every disposable, but is 1466 | // good housekeeping since these objects will no longer be useable. 1467 | this.#socket = undefined; 1468 | this.#channel = undefined; 1469 | this.#disposables = undefined; 1470 | 1471 | // Dispose all resources in `disposables` 1472 | await disposables[Symbol.asyncDispose](); 1473 | } 1474 | } 1475 | } 1476 | ``` 1477 | 1478 | ### Subclassing `Disposable` Classes 1479 | 1480 | You can also use a `DisposableStack` to assist with disposal in a subclass constructor whose superclass is disposable: 1481 | 1482 | ```js 1483 | class DerivedPluginHost extends PluginHost { 1484 | constructor() { 1485 | super(); 1486 | 1487 | // Create a DisposableStack to cover the subclass constructor. 1488 | using stack = new DisposableStack(); 1489 | 1490 | // Defer a callback to dispose resources on the superclass. We use `defer` so that we can invoke the version of 1491 | // `[Symbol.dispose]` on the superclass and not on this or any subclasses. 1492 | stack.defer(() => super[Symbol.dispose]()); 1493 | 1494 | // If any operations throw during subclass construction, the instance will still be disposed, and superclass 1495 | // resources will be freed 1496 | doSomethingThatCouldPotentiallyThrow(); 1497 | 1498 | // As the last step before exiting, empty out the DisposableStack so that we don't dispose ourselves. 1499 | stack.move(); 1500 | } 1501 | } 1502 | ``` 1503 | 1504 | Here, we can use `stack` to track the result of `super()` (i.e., the `this` value). If any exception occurs during 1505 | subclass construction, we can ensure that `[Symbol.dispose]()` is called, freeing resources. If the subclass also needs 1506 | to track its own disposable resources, this example is modified slightly: 1507 | 1508 | ```js 1509 | class DerivedPluginHostWithOwnDisposables extends PluginHost { 1510 | #logger; 1511 | #disposables; 1512 | 1513 | constructor() { 1514 | super() 1515 | 1516 | // Create a DisposableStack to cover the subclass constructor. 1517 | using stack = new DisposableStack(); 1518 | 1519 | // Defer a callback to dispose resources on the superclass. We use `defer` so that we can invoke the version of 1520 | // `[Symbol.dispose]` on the superclass and not on this or any subclasses. 1521 | stack.defer(() => super[Symbol.dispose]()); 1522 | 1523 | // Create a logger that uses the file system and add it to our own disposables. 1524 | this.#logger = stack.use(new FileLogger()); 1525 | 1526 | // If any operations throw during subclass construction, the instance will still be disposed, and superclass 1527 | // resources will be freed 1528 | doSomethingThatCouldPotentiallyThrow(); 1529 | 1530 | // Persist our own disposables. If construction fails prior to the call to `stack.move()`, our own disposables 1531 | // will be disposed before they are set, and then the superclass `[Symbol.dispose]` will be invoked. 1532 | this.#disposables = stack.move(); 1533 | } 1534 | 1535 | [Symbol.dispose]() { 1536 | this.#logger = undefined; 1537 | 1538 | // Dispose of our resources and those of our superclass. We do not need to invoke `super[Symbol.dispose]()` since 1539 | // that is already tracked by the `stack.defer` call in the constructor. 1540 | this.#disposables[Symbol.dispose](); 1541 | } 1542 | } 1543 | ``` 1544 | 1545 | In this example, we can simply add new resources to the `stack` and move its contents into the subclass instance's 1546 | `this.#disposables`. In the subclass `[Symbol.dispose]()` method we don't need to call `super[Symbol.dispose]()` since 1547 | that has already been tracked by the `stack.defer` call in the constructor. 1548 | 1549 | # Relation to `Iterator` and `for..of` 1550 | 1551 | Iterators in ECMAScript also employ a "cleanup" step by way of supplying a `return` method. This means that there is 1552 | some similarity between a `using` declaration and a `for..of` statement: 1553 | 1554 | ```js 1555 | // using 1556 | function f() { 1557 | using x = ...; 1558 | // use x 1559 | } // x is disposed 1560 | 1561 | 1562 | // for..of 1563 | function makeDisposableScope() { 1564 | const resources = []; 1565 | let state = 0; 1566 | return { 1567 | next() { 1568 | switch (state) { 1569 | case 0: 1570 | state++; 1571 | return { 1572 | done: false, 1573 | value: { 1574 | use(value) { 1575 | resources.unshift(value); 1576 | return value; 1577 | } 1578 | } 1579 | }; 1580 | case 1: 1581 | state++; 1582 | for (const value of resources) { 1583 | value?.[Symbol.dispose](); 1584 | } 1585 | default: 1586 | state = -1; 1587 | return { done: true }; 1588 | } 1589 | }, 1590 | return() { 1591 | switch (state) { 1592 | case 1: 1593 | state++; 1594 | for (const value of resources) { 1595 | value?.[Symbol.dispose](); 1596 | } 1597 | default: 1598 | state = -1; 1599 | return { done: true }; 1600 | } 1601 | }, 1602 | [Symbol.iterator]() { return this; } 1603 | } 1604 | } 1605 | 1606 | function f() { 1607 | for (const { use } of makeDisposableScope()) { 1608 | const x = use(...); 1609 | // use x 1610 | } // x is disposed 1611 | } 1612 | ``` 1613 | 1614 | However there are a number drawbacks to using `for..of` as an alternative: 1615 | 1616 | - Exceptions in the body are swallowed by exceptions from disposables. 1617 | - `for..of` implies iteration, which can be confusing when reading code. 1618 | - Conflating `for..of` and resource management could make it harder to find documentation, examples, StackOverflow 1619 | answers, etc. 1620 | - A `for..of` implementation like the one above cannot control the scope of `use`, which can make lifetimes confusing: 1621 | ```js 1622 | for (const { use } of ...) { 1623 | const x = use(...); // ok 1624 | setImmediate(() => { 1625 | const y = use(...); // wrong lifetime 1626 | }); 1627 | } 1628 | ``` 1629 | - Significantly more boilerplate compared to `using`. 1630 | - Mandates introduction of a new block scope, even at the top level of a function body. 1631 | - Control flow analysis of a `for..of` loop cannot infer definite assignment since a loop could potentially have zero 1632 | elements: 1633 | ```js 1634 | // using 1635 | function f1() { 1636 | /** @type {string | undefined} */ 1637 | let x; 1638 | { 1639 | using y = ...; 1640 | x = y.text; 1641 | } 1642 | x.toString(); // x is definitely assigned 1643 | } 1644 | 1645 | // for..of 1646 | function f2() { 1647 | /** @type {string | undefined} */ 1648 | let x; 1649 | for (const { use } of ...) { 1650 | const y = use(...); 1651 | x = y.text; 1652 | } 1653 | x.toString(); // possibly an error in a static analyzer since `x` is not guaranteed to have been assigned. 1654 | } 1655 | ``` 1656 | - Using `continue` and `break` is more difficult if you need to dispose of an iterated value: 1657 | ```js 1658 | // using 1659 | for (using x of iterable) { 1660 | if (!x.ready) continue; 1661 | if (x.done) break; 1662 | ... 1663 | } 1664 | 1665 | // for..of 1666 | outer: for (const x of iterable) { 1667 | for (const { use } of ...) { 1668 | use(x); 1669 | if (!x.ready) continue outer; 1670 | if (!x.done) break outer; 1671 | ... 1672 | } 1673 | } 1674 | ``` 1675 | 1676 | # Relation to DOM APIs 1677 | 1678 | This proposal does not necessarily require immediate support in the HTML DOM specification, as existing APIs can still 1679 | be adapted by using `DisposableStack` or `AsyncDisposableStack`. However, there are a number of APIs that could benefit 1680 | from this proposal and should be considered by the relevant standards bodies. The following is by no means a complete 1681 | list, and primarily offers suggestions for consideration. The actual implementation is at the discretion of the relevant 1682 | standards bodies. 1683 | 1684 | - `AudioContext` — `@@asyncDispose()` as an alias or [wrapper][] for `close()`. 1685 | - NOTE: `close()` here is asynchronous, but uses the same name as similar synchronous methods on other objects. 1686 | - `BroadcastChannel` — `@@dispose()` as an alias or [wrapper][] for `close()`. 1687 | - `EventSource` — `@@dispose()` as an alias or [wrapper][] for `close()`. 1688 | - `FileReader` — `@@dispose()` as an alias or [wrapper][] for `abort()`. 1689 | - `IDbTransaction` — `@@dispose()` could invoke `abort()` if the transaction is still in the active state: 1690 | ```js 1691 | { 1692 | using tx = db.transaction(storeNames); 1693 | // ... 1694 | if (...) throw new Error(); 1695 | // ... 1696 | tx.commit(); 1697 | } // implicit tx.abort() if we don't reach the explicit tx.commit() 1698 | ``` 1699 | - `ImageBitmap` — `@@dispose()` as an alias or [wrapper][] for `close()`. 1700 | - `IntersectionObserver` — `@@dispose()` as an alias or [wrapper][] for `disconnect()`. 1701 | - `MediaKeySession` — `@@asyncDispose()` as an alias or [wrapper][] for `close()`. 1702 | - NOTE: `close()` here is asynchronous, but uses the same name as similar synchronous methods on other objects. 1703 | - `MessagePort` — `@@dispose()` as an alias or [wrapper][] for `close()`. 1704 | - `MutationObserver` — `@@dispose()` as an alias or [wrapper][] for `disconnect()`. 1705 | - `PaymentRequest` — `@@asyncDispose()` could invoke `abort()` if the payment is still in the active state. 1706 | - NOTE: `abort()` here is asynchronous, but uses the same name as similar synchronous methods on other objects. 1707 | - `PerformanceObserver` — `@@dispose()` as an alias or [wrapper][] for `disconnect()`. 1708 | - `PushSubscription` — `@@asyncDispose()` as an alias or [wrapper][] for `unsubscribe()`. 1709 | - `ReadableStream` — `@@asyncDispose()` as an alias or [wrapper][] for `cancel()`. 1710 | - `ReadableStreamDefaultReader` — Either `@@dispose()` as an alias or [wrapper][] for `releaseLock()`, or 1711 | `@@asyncDispose()` as a [wrapper][] for `cancel()` (but probably not both). 1712 | - `RTCPeerConnection` — `@@dispose()` as an alias or [wrapper][] for `close()`. 1713 | - `RTCRtpTransceiver` — `@@dispose()` as an alias or [wrapper][] for `stop()`. 1714 | - `ReadableStreamDefaultController` — `@@dispose()` as an alias or [wrapper][] for `close()`. 1715 | - `ReadableStreamDefaultReader` — Either `@@dispose()` as an alias or [wrapper][] for `releaseLock()`, or 1716 | - `ResizeObserver` — `@@dispose()` as an alias or [wrapper][] for `disconnect()`. 1717 | - `ServiceWorkerRegistration` — `@@asyncDispose()` as a [wrapper][] for `unregister()`. 1718 | - `SourceBuffer` — `@@dispose()` as a [wrapper][] for `abort()`. 1719 | - `TransformStreamDefaultController` — `@@dispose()` as an alias or [wrapper][] for `terminate()`. 1720 | - `WebSocket` — `@@dispose()` as a [wrapper][] for `close()`. 1721 | - `Worker` — `@@dispose()` as an alias or [wrapper][] for `terminate()`. 1722 | - `WritableStream` — `@@asyncDispose()` as an alias or [wrapper][] for `close()`. 1723 | - NOTE: `close()` here is asynchronous, but uses the same name as similar synchronous methods on other objects. 1724 | - `WritableStreamDefaultWriter` — Either `@@dispose()` as an alias or [wrapper][] for `releaseLock()`, or 1725 | `@@asyncDispose()` as a [wrapper][] for `close()` (but probably not both). 1726 | - `XMLHttpRequest` — `@@dispose()` as an alias or [wrapper][] for `abort()`. 1727 | 1728 | In addition, several new APIs could be considered that leverage this functionality: 1729 | 1730 | - `EventTarget.prototype.addEventListener(type, listener, { subscription: true }) -> Disposable` — An option 1731 | passed to `addEventListener` could 1732 | return a `Disposable` that removes the event listener when disposed. 1733 | - `Performance.prototype.measureBlock(measureName, options) -> Disposable` — Combines `mark` and `measure` into a 1734 | block-scoped disposable: 1735 | ```js 1736 | function f() { 1737 | using measure = performance.measureBlock("f"); // marks on entry 1738 | // ... 1739 | } // marks and measures on exit 1740 | ``` 1741 | - `SVGSVGElement` — A new method producing a [single-use disposer][] for `pauseAnimations()` and `unpauseAnimations()`. 1742 | - `ScreenOrientation` — A new method producing a [single-use disposer][] for `lock()` and `unlock()`. 1743 | 1744 | ### Definitions 1745 | 1746 | A _wrapper for `x()`_ is a method that invokes `x()`, but only if the object is in a state 1747 | such that calling `x()` will not throw as a result of repeated evaluation. 1748 | 1749 | A _callback-adapting wrapper_ is a _wrapper_ that adapts a continuation passing-style method 1750 | that accepts a callback into a `Promise`-producing method. 1751 | 1752 | A _single-use disposer for `x()` and `y()`_ indicates a newly constructed disposable object 1753 | that invokes `x()` when constructed and `y()` when disposed the first time (and does nothing if the object is disposed 1754 | more than once). 1755 | 1756 | # Relation to NodeJS APIs 1757 | 1758 | This proposal does not necessarily require immediate support in NodeJS, as existing APIs can still be adapted by using 1759 | `DisposableStack` or `AsyncDisposableStack`. However, there are a number of APIs that could benefit from this proposal 1760 | and should be considered by the NodeJS maintainers. The following is by no means a complete list, and primarily offers 1761 | suggestions for consideration. The actual implementation is at the discretion of the NodeJS maintainers. 1762 | 1763 | - Anything with `ref()` and `unref()` methods — A new method or API that produces a [single-use disposer][] for 1764 | `ref()` and `unref()`. 1765 | - Anything with `cork()` and `uncork()` methods — A new method or API that produces a [single-use disposer][] for 1766 | `cork()` and `uncork()`. 1767 | - `async_hooks.AsyncHook` — either `@@dispose()` as an alias or [wrapper][] for `disable()`, or a new method that 1768 | produces a [single-use disposer][] for `enable()` and `disable()`. 1769 | - `child_process.ChildProcess` — `@@dispose()` as an alias or [wrapper][] for `kill()`. 1770 | - `cluster.Worker` — `@@dispose()` as an alias or [wrapper][] for `kill()`. 1771 | - `crypto.Cipher`, `crypto.Decipher` — `@@dispose()` as a [wrapper][] for `final()`. 1772 | - `crypto.Hash`, `crypto.Hmac` — `@@dispose()` as a [wrapper][] for `digest()`. 1773 | - `dns.Resolver`, `dnsPromises.Resolver` — `@@dispose()` as an alias or [wrapper][] for `cancel()`. 1774 | - `domain.Domain` — A new method or API that produces a [single-use disposer][] for `enter()` and `exit()`. 1775 | - `events.EventEmitter` — A new method or API that produces a [single-use disposer][] for `on()` and `off()`. 1776 | - `fs.promises.FileHandle` — `@@asyncDispose()` as an alias or [wrapper][] for `close()`. 1777 | - `fs.Dir` — `@@asyncDispose()` as an alias or [wrapper][] for `close()`, `@@dispose()` as an alias or [wrapper][] 1778 | for `closeSync()`. 1779 | - `fs.FSWatcher` — `@@dispose()` as an alias or [wrapper][] for `close()`. 1780 | - `http.Agent` — `@@dispose()` as an alias or [wrapper][] for `destroy()`. 1781 | - `http.ClientRequest` — Either `@@dispose()` or `@@asyncDispose()` as an alias or [wrapper][] for `destroy()`. 1782 | - `http.Server` — `@@asyncDispose()` as a [callback-adapting wrapper][] for `close()`. 1783 | - `http.ServerResponse` — `@@asyncDispose()` as a [callback-adapting wrapper][] for `end()`. 1784 | - `http.IncomingMessage` — Either `@@dispose()` or `@@asyncDispose()` as an alias or [wrapper][] for `destroy()`. 1785 | - `http.OutgoingMessage` — Either `@@dispose()` or `@@asyncDispose()` as an alias or [wrapper][] for `destroy()`. 1786 | - `http2.Http2Session` — `@@asyncDispose()` as a [callback-adapting wrapper][] for `close()`. 1787 | - `http2.Http2Stream` — `@@asyncDispose()` as a [callback-adapting wrapper][] for `close()`. 1788 | - `http2.Http2Server` — `@@asyncDispose()` as a [callback-adapting wrapper][] for `close()`. 1789 | - `http2.Http2SecureServer` — `@@asyncDispose()` as a [callback-adapting wrapper][] for `close()`. 1790 | - `http2.Http2ServerRequest` — Either `@@dispose()` or `@@asyncDispose()` as an alias or [wrapper][] for 1791 | `destroy()`. 1792 | - `http2.Http2ServerResponse` — `@@asyncDispose()` as a [callback-adapting wrapper][] for `end()`. 1793 | - `https.Server` — `@@asyncDispose()` as a [callback-adapting wrapper][] for `close()`. 1794 | - `inspector` — A new API that produces a [single-use disposer][] for `open()` and `close()`. 1795 | - `stream.Writable` — Either `@@dispose()` or `@@asyncDispose()` as an alias or [wrapper][] for `destroy()` or 1796 | `@@asyncDispose` only as a [callback-adapting wrapper][] for `end()` (depending on whether the disposal behavior 1797 | should be to drop immediately or to flush any pending writes). 1798 | - `stream.Readable` — Either `@@dispose()` or `@@asyncDispose()` as an alias or [wrapper][] for `destroy()`. 1799 | - ... and many others in `net`, `readline`, `tls`, `udp`, and `worker_threads`. 1800 | 1801 | # Meeting Notes 1802 | 1803 | * [TC39 July 24th, 2018](https://github.com/tc39/notes/blob/main/meetings/2018-07/july-24.md#explicit-resource-management) 1804 | - [Conclusion](https://github.com/tc39/notes/blob/main/meetings/2018-07/july-24.md#conclusionresolution-7) 1805 | - Stage 1 acceptance 1806 | * [TC39 July 23rd, 2019](https://github.com/tc39/notes/blob/main/meetings/2019-07/july-23.md#explicit-resource-management) 1807 | - [Conclusion](https://github.com/tc39/notes/blob/main/meetings/2019-07/july-23.md#conclusionresolution-7) 1808 | - Table until Thursday, inconclusive. 1809 | * [TC39 July 25th, 2019](https://github.com/tc39/notes/blob/main/meetings/2019-07/july-25.md#explicit-resource-management-for-stage-2-continuation-from-tuesday) 1810 | - [Conclusion](https://github.com/tc39/notes/blob/main/meetings/2019-07/july-25.md#conclusionresolution-7): 1811 | - Investigate Syntax 1812 | - Approved for Stage 2 1813 | - YK (@wycatz) & WH (@waldemarhorwat) will be stage 3 reviewers 1814 | * [TC39 October 10th, 2021](https://github.com/tc39/notes/blob/main/meetings/2021-10/oct-27.md#explicit-resource-management-update) 1815 | - [Conclusion](https://github.com/tc39/notes/blob/main/meetings/2021-10/oct-27.md#conclusionresolution-1) 1816 | - Status Update only 1817 | - WH Continuing to review 1818 | - SYG (@syg) added as reviewer 1819 | * [TC39 December 1st, 2022](https://github.com/tc39/notes/blob/main/meetings/2022-11/dec-01.md#explicit-resource-management-for-stage-3) 1820 | - [Conclusion](https://github.com/tc39/notes/blob/main/meetings/2022-11/dec-01.md#conclusionresolution-5) 1821 | - `using` declarations, `Symbol.dispose`, and `DisposableStack` advanced to Stage 3, under the following conditions: 1822 | - Resolution of [#103 - Argument order for `adopt()`](https://github.com/tc39/proposal-explicit-resource-management/issues/130) 1823 | - Deferral of `async using` declarations, `Symbol.asyncDispose`, and `AsyncDisposableStack`. 1824 | - async `using` declarations, `Symbol.asyncDispose`, and `AsyncDisposableStack` remain at Stage 2 as an independent 1825 | proposal. 1826 | * [TC39 January 31st, 2023](https://github.com/tc39/notes/blob/main/meetings/2023-01/jan-31.md#explicit-resource-management-stage-3-update) 1827 | - [Conclusion](https://github.com/tc39/notes/blob/main/meetings/2023-01/jan-31.md#conclusionresolution-3) 1828 | - Ban `await` as identifier in `using` (#138) was accepted 1829 | - Support `using` at top level of `eval` (#136) was rejected 1830 | - May consider a needs-consensus PR in the future based on implementer/community feedback. 1831 | * [TC39 February 1st, 2023](https://github.com/tc39/notes/blob/main/meetings/2023-01/feb-01.md#async-explicit-resource-management) 1832 | - [Conclusion](https://github.com/tc39/notes/blob/main/meetings/2023-01/feb-01.md#conclusionresolution-5) 1833 | - Rename `Symbol.asyncDispose` to `Symbol.disposeAsync` was rejected 1834 | - Conditional advancement to Stage 3 at March 2023 plenary pending outcome of investigation into `async using` vs. 1835 | `using await` syntax. 1836 | * [TC39 March 21st, 2023](https://github.com/tc39/notes/blob/main/meetings/2023-03/mar-21.md#async-explicit-resource-management) 1837 | - [Conclusion](https://github.com/tc39/notes/blob/main/meetings/2023-03/mar-21.md#conclusion-8) 1838 | - Committee resolves to adopt `await using` pending investigation of potential cover grammar. 1839 | * [TC39 March 23rd, 2023](https://github.com/tc39/notes/blob/main/meetings/2023-03/mar-23.md#async-explicit-resource-management-again) 1840 | - [Conclusion](https://github.com/tc39/notes/blob/main/meetings/2023-03/mar-23.md#conclusion-5) 1841 | - Stage 3, conditionally on final review of cover grammar by Waldemar Horwat. 1842 | - Consensus on normative change to remove `await` identifier restriction for `using` declarations. 1843 | 1844 | # TODO 1845 | 1846 | The following is a high-level list of tasks to progress through each stage of the [TC39 proposal process](https://tc39.github.io/process-document/): 1847 | 1848 | ### Stage 1 Entrance Criteria 1849 | 1850 | * [x] Identified a "[champion][Champion]" who will advance the addition. 1851 | * [x] [Prose][Prose] outlining the problem or need and the general shape of a solution. 1852 | * [x] Illustrative [examples][Examples] of usage. 1853 | * [x] High-level [API][API]. 1854 | 1855 | ### Stage 2 Entrance Criteria 1856 | 1857 | * [x] [Initial specification text][Specification]. 1858 | * [ ] [Transpiler support][Transpiler] (_Optional_). 1859 | 1860 | ### Stage 3 Entrance Criteria 1861 | 1862 | * [x] [Complete specification text][Specification]. 1863 | * [x] Designated reviewers have signed off on the current spec text: 1864 | * [x] [Waldemar Horwat][Stage3Reviewer1] has [signed off][Stage3Reviewer1SignOff] 1865 | * [x] [Shu-yu Guo][Stage3Reviewer2] has [signed off][Stage3Reviewer2SignOff] 1866 | * [x] The [ECMAScript editor][Stage3Editor] has [signed off][Stage3EditorSignOff] on the current spec text. 1867 | 1868 | ### Stage 4 Entrance Criteria 1869 | 1870 | * [ ] [Test262](https://github.com/tc39/test262) acceptance tests have been written for mainline usage scenarios and [merged][Test262PullRequest]. 1871 | * [ ] Two compatible implementations which pass the acceptance tests: [\[1\]][Implementation1], [\[2\]][Implementation2]. 1872 | * [x] A [pull request][Ecma262PullRequest] has been sent to tc39/ecma262 with the integrated spec text. 1873 | * [ ] The ECMAScript editor has signed off on the [pull request][Ecma262PullRequest]. 1874 | 1875 | ## Implementations 1876 | 1877 | - Built-ins from this proposal are available in [`core-js`](https://github.com/zloirock/core-js#explicit-resource-management) 1878 | 1879 | 1880 | 1881 | 1882 | 1883 | 1884 | 1885 | 1886 | 1887 | 1888 | 1889 | 1890 | 1891 | 1892 | 1893 | 1894 | 1895 | 1896 | 1897 | [Champion]: #status 1898 | [Prose]: #motivations 1899 | [Examples]: #examples 1900 | [API]: #api 1901 | [Specification]: https://arai-a.github.io/ecma262-compare/?pr=3000 1902 | [Transpiler]: #todo 1903 | [Stage3Reviewer1]: https://github.com/tc39/proposal-explicit-resource-management/issues/71 1904 | [Stage3Reviewer1SignOff]: https://github.com/tc39/proposal-explicit-resource-management/issues/71#issuecomment-1325842256 1905 | [Stage3Reviewer2]: https://github.com/tc39/proposal-explicit-resource-management/issues/93 1906 | [Stage3Reviewer2SignOff]: #todo 1907 | [Stage3Editor]: https://github.com/tc39/proposal-explicit-resource-management/issues/72 1908 | [Stage3EditorSignOff]: #todo 1909 | [Test262PullRequest]: #todo 1910 | [Implementation1]: #todo 1911 | [Implementation2]: #todo 1912 | [Ecma262PullRequest]: https://github.com/tc39/ecma262/pull/3000 1913 | [wrapper]: #wrapper 1914 | [callback-adapting wrapper]: #adapter 1915 | [single-use disposer]: #disposer 1916 | -------------------------------------------------------------------------------- /future/using-await-statement.md: -------------------------------------------------------------------------------- 1 | # `using await` Statements for ECMAScript 2 | 3 | A `using await` statement is a variation of the [`using` statement](./using-statement.md) that is designed to work with 4 | Async Disposable objects, which allow async logic to be evaluated and awaited during disposal. A `using await` 5 | statement is only permitted in an `[+Await]` context such as the top level of a _Module_ or inside the body of an 6 | async function. 7 | 8 | # Example 9 | 10 | ```js 11 | class Logger { 12 | write(message) { ... } 13 | ... 14 | #disposed = false; 15 | async [Symbol.asyncDispose]() { 16 | if (!this.#disposed) { 17 | this.#disposed = true; 18 | await this.#flush(); // flush any pending log file writes asynchronously. 19 | this.#close(); // close file handle. 20 | } 21 | } 22 | } 23 | 24 | async function main() { 25 | using await (logger = new Logger()) { // create logger, async dispose at end of block 26 | logger.write("started"); 27 | ... 28 | logger.write("done"); 29 | 30 | // implicit `await logger[Symbol.asyncDispose]()` when block exits regardless as to 31 | // whether by `return`, `throw`, `break`, or `continue`. 32 | } 33 | } 34 | ``` 35 | 36 | # Status 37 | 38 | **Status:** Out of scope 39 | 40 | The `using await` statement has been postponed and deemed out of scope for the original proposal. This was cut primarily 41 | to reduce the scope of the intial proposal, but also due to the inconsistency between the RAII-style 42 | [`using` declaration](../README.md) and the block-style of the `using await` statement that exists now that the 43 | block-style [`using` statement](./using-statement.md) is also out of scope. 44 | 45 | # Alternatives 46 | 47 | This could also potentially be addressed by the [`using await` declaration](./using-await-declaration.md). 48 | 49 | # Postponement Implications 50 | 51 | Postponing `using await` statements without also introducing `using await` declarations means that we lose the ability 52 | to declaratively register disposal of resources that require async cleanup steps. This affects use cases such as 53 | three-phase commit (3PC) transactions and some async file I/O (i.e., async flush). These still can be accomplished 54 | imperatively via `await obj[Symbol.asyncDispose]()`, but the lack of a syntactic declaration reduces their utility. 55 | 56 | # More Information 57 | 58 | An early draft of the spec text for `using await` statements can be found in #86. 59 | -------------------------------------------------------------------------------- /future/using-statement.md: -------------------------------------------------------------------------------- 1 | # `using` Statements for ECMAScript 2 | 3 | A `using` statement is a block-style variation of the RAII-style [`using` declaration](../README.md). 4 | 5 | # Example 6 | 7 | ```js 8 | // introducing a binding 9 | using (res = expr) { // evaluate 'expr' and bind to 'res', which is disposed at the end of the block 10 | ... 11 | 12 | } // 'res' is disposed (calls 'res[Symbol.dispose]()') 13 | 14 | 15 | // multiple bindings 16 | using (res1 = expr1, res2 = expr2) { 17 | ... 18 | } // res2 is disposed, then res1 is disposed 19 | 20 | 21 | // no binding 22 | using (void = expr1) { // evaluate 'expr1' and store result in implicit variable 23 | 24 | } // implicit variable is disposed 25 | ``` 26 | 27 | # Status 28 | 29 | **Status:** Out of scope 30 | 31 | The `using` statement has been postponed and deemed out of scope for the original proposal. This was cut primarily 32 | to reduce the scope of the intial proposal and to focus on the highly preferred RAII-style. 33 | 34 | # Alternatives 35 | 36 | This could also potentially be addressed by the [`using await` declaration](./using-await-declaration.md). 37 | 38 | # Postponement Implications 39 | 40 | Though the `using` statement provided a bridge between RAII-style [`using` declarations](../README.md) and the 41 | block-style [`using await` statement](./using-await-statement.md). Postponing the `using` statement may decrease the 42 | likelihood that the `using await` statement could advance in a version of ECMAScript that already had `using` 43 | declarations. 44 | 45 | # History 46 | 47 | The initial draft of this proposal used the `using` statement syntax, as well as the following alternatives, before the 48 | proposal settled on the RAII-style `using` declaration form: 49 | 50 | ```js 51 | // initial draft 52 | using (const res = expr) { ... } 53 | 54 | // Java try-with-resources variant 55 | try (const res = expr) { ... } 56 | try (const res = expr) { ... } finally { } 57 | 58 | // other variations 59 | try using (const res = expr) { ... } 60 | try using (const res = expr) { ... } finally { } 61 | 62 | // final version 63 | using (res = expr) { ... } 64 | ``` 65 | 66 | # More Information 67 | 68 | An early draft of the spec text for `using` statements can be found in #86. 69 | -------------------------------------------------------------------------------- /future/using-void-declaration.md: -------------------------------------------------------------------------------- 1 | # `using void` Declarations for ECMAScript 2 | 3 | A `using void` declaration is a bindingless variant of the RAII-style [`using` declaration](../README.md). With this 4 | form, a `void` keyword may be substituted in place of a _BindingIdentifier_. In this case, a user-accessible 5 | block-scoped binding is not created for the result of the expression, but that result may still participate in 6 | disposal at the end of the block. 7 | 8 | The `using void` variant was also present in the [`using` statement](./using-statement.md), 9 | [`using await` statement](./using-await-statement.md), and [`using await` declaration](./using-await-declaration.md) 10 | proposals, which are now also out of scope. 11 | 12 | # Example 13 | 14 | ```js 15 | // block-scoped resource, no binding 16 | { 17 | using void = expr1; // 'expr1' is evaluated and result is captured until the end of the block. 18 | ... 19 | } // result is disposed 20 | 21 | 22 | // multiple bindingless resources 23 | { 24 | using void = expr1, void = expr2; 25 | ... 26 | } // result of expr2 is disposed, then result of expr1 is disposed 27 | 28 | 29 | // mixing bindings and bindingless forms 30 | { 31 | using x = expr1, void = expr2, y = expr3; 32 | 33 | } // y is disposed, then result of expr2 is disposed, then x is disposed 34 | 35 | 36 | // in a 'using' statement 37 | using (void = expr1) { ... } 38 | 39 | 40 | // in a 'using await' statement 41 | using await (void = expr1) { ... } 42 | 43 | 44 | // in a `using await` declaration 45 | { 46 | using await void = expr1; 47 | ... 48 | } // result of expr1 is asynchronously disposed 49 | ``` 50 | 51 | # Status 52 | 53 | **Status:** Out of scope 54 | 55 | The `using void` declaration has been postponed and deemed out of scope for the original proposal. This was cut 56 | primarily to reduce the scope of the intial proposal, though we believe a bindingless form would still be invaluable 57 | for many use cases such as locking, logging, etc.: 58 | 59 | ```js 60 | // locking a resource 61 | function useResource() { 62 | // NOTE: `mutex.lock()` blocks the thread until it can take a lock, returning a lock handle object with a 63 | // `[Symbol.dispose]` method that releases the lock at the end of the block. 64 | 65 | using void = mutex.lock(); // binding would be unused, potentially causing linters to complain. 66 | 67 | res.doSomething(); 68 | 69 | } // The lock handle object is disposed. 70 | 71 | 72 | // activity logging 73 | class Activity { 74 | #name; 75 | #start; 76 | #disposed = false; 77 | constructor(name) { 78 | this.#name = name; 79 | this.#start = Date.now(); 80 | console.log(`Activity '${name}' started.`); 81 | } 82 | 83 | [Symbol.dispose]() { 84 | if (!this.#disposed) { 85 | this.#disposd = true; 86 | const end = Date.now(); 87 | console.log(`Activity '${name}' ended. Took ${end - start} ms.`); 88 | } 89 | } 90 | } 91 | 92 | function operation1() { 93 | using void = new Activity("operation1"); 94 | operation2(); 95 | } 96 | 97 | function operation2() { 98 | using void = new Activity("operation2"); 99 | console.log("some long running operation..."); 100 | } 101 | 102 | operation1(); 103 | // Logs: 104 | // Activity 'operation1' started. 105 | // Activity 'operation2' started. 106 | // some long running operation... 107 | // Activity 'operation2' ended. Took ? ms. 108 | // Activity 'operation1' ended. Took ? ms. 109 | ``` 110 | 111 | # Alternatives 112 | 113 | There is no currently proposed alternative that avoids introducing an unnecessary binding. In these cases, its likely 114 | that users will do something like: 115 | 116 | ```js 117 | using _ = expr; 118 | ``` 119 | 120 | or 121 | 122 | ```js 123 | using dummy = expr; // eslint-disable-line no-unused-vars 124 | ``` 125 | 126 | # Postponement Implications 127 | 128 | The `using void` declaration is more of a "nice to have" feature to avoid needing to name otherwise unreferenced 129 | resources, where the side-effects of the `[Symbol.dispose]` method invoked at the end of the block are desired, or 130 | when the desire is to leverage an effect similar to Go's `defer`. 131 | 132 | # More Information 133 | 134 | An early draft of the spec text supporting `using void` declarations can be found in #86. 135 | 136 | # Explainer Snapshot 137 | 138 | The following sections were originally part of the [explainer](../README.md). 139 | 140 | > # Semantics 141 | > 142 | > ## `using` Declarations 143 | > 144 | > ### `using` Declarations with Existing Resources 145 | > 146 | > ```grammarkdown 147 | > UsingDeclaration : 148 | > `using` BindingList `;` 149 | > `using` `await` BindingList `;` 150 | > 151 | > LexicalBinding : 152 | > `void` Initializer 153 | > ``` 154 | > 155 | > When a `using` declaration is parsed with `void` _Initializer_, an implicit block-scoped binding is created for the 156 | > result of the expression. When the _Block_ or _Module_ immediately containing the `using` declaration is exited, 157 | > whether by an abrupt or normal completion, `[Symbol.dispose]()` is called on the implicit binding as long as it is 158 | > neither `null` nor `undefined`. If an error is thrown in both the containing _Block_/_Module_ and the call to 159 | > `[Symbol.dispose]()`, an `AggregateError` containing both errors will be thrown instead. 160 | > 161 | > ```js 162 | > { 163 | > ... // (1) 164 | > using void = expr; // in Block scope 165 | > ... // (2) 166 | > } 167 | > ``` 168 | > 169 | > The above example has similar runtime semantics as the following transposed representation: 170 | > 171 | > ```js 172 | > { 173 | > const $$try = { stack: [], exception: undefined }; 174 | > try { 175 | > ... // (1) 176 | > 177 | > const $$expr = expr; // evaluate `expr` 178 | > if ($$expr !== null && $$expr !== undefined) { 179 | > const $$dispose = $$expr[Symbol.dispose]; 180 | > if (typeof $$dispose !== "function") { 181 | > throw new TypeError(); 182 | > } 183 | > $$try.stack.push({ value: $$expr, dispose: $$dispose }); 184 | > } 185 | > 186 | > ... // (2) 187 | > } 188 | > catch ($$error) { 189 | > $$try.exception = { cause: $$error }; 190 | > } 191 | > finally { 192 | > const $$errors = []; 193 | > while ($$try.stack.length) { 194 | > const { value: $$expr, dispose: $$dispose } = $$try.stack.pop(); 195 | > try { 196 | > $$dispose.call($$expr); 197 | > } 198 | > catch ($$error) { 199 | > $$errors.push($$error); 200 | > } 201 | > } 202 | > if ($$errors.length > 0) { 203 | > throw new AggregateError($$errors, undefined, $$try.exception); 204 | > } 205 | > if ($$try.exception) { 206 | > throw $$try.exception.cause; 207 | > } 208 | > } 209 | > } 210 | > ``` 211 | > 212 | > The local block-scoped binding ensures that if `expr` above is reassigned, we still correctly close the resource we are 213 | > explicitly tracking. 214 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const del = require("del"); 2 | const path = require("path"); 3 | const gulp = require("gulp"); 4 | const emu = require("gulp-emu"); 5 | const rename = require("gulp-rename"); 6 | const gls = require("gulp-live-server"); 7 | 8 | gulp.task("clean", () => del("docs/**/*")); 9 | 10 | gulp.task("build", () => gulp 11 | .src(["spec.emu"]) 12 | .pipe(emu({ 13 | log: require("ecmarkup/lib/utils").logVerbose, 14 | warn: err => { 15 | const file = path.resolve(err.file || "spec.emu"); 16 | const message = `Warning: ${file}:${typeof err.line === "number" ? `${err.line}:${err.column}:` : ""} ${err.message}`; 17 | require("ecmarkup/lib/utils").logWarning(message); 18 | }, 19 | ecma262Biblio: false, 20 | })) 21 | .pipe(rename("index.html")) 22 | .pipe(gulp.dest("docs"))); 23 | 24 | gulp.task("watch", () => gulp 25 | .watch(["spec.emu"], gulp.task("build"))); 26 | 27 | gulp.task("start", gulp.parallel("watch", () => { 28 | const server = gls.static("docs", 8080); 29 | const promise = server.start(); 30 | (/** @type {import("chokidar").FSWatcher}*/(gulp.watch(["docs/**/*"]))) 31 | .on("change", file => { 32 | server.notify({ path: path.resolve(file) }); 33 | }); 34 | return promise; 35 | })); 36 | 37 | gulp.task("default", gulp.task("build")); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proposal-explicit-resource-management", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "ECMAScript explicit resource management", 6 | "homepage": "https://github.com/tc39/proposal-explicit-resource-management#readme", 7 | "author": { 8 | "name": "Ron Buckton", 9 | "email": "ron.buckton@microsoft.com" 10 | }, 11 | "keywords": [ 12 | "javascript", 13 | "ecmascript" 14 | ], 15 | "scripts": { 16 | "compile": "gulp build", 17 | "start": "gulp start" 18 | }, 19 | "license": "SEE LICENSE IN https://tc39.es/ecma262/#sec-copyright-and-software-license", 20 | "devDependencies": { 21 | "@tc39/ecma262-biblio": "^2.0.2322", 22 | "del": "^6.0.0", 23 | "ecmarkup": "^12.1.0", 24 | "gulp": "^4.0.2", 25 | "gulp-emu": "^2.1.0", 26 | "gulp-live-server": "0.0.31", 27 | "gulp-rename": "^2.0.0" 28 | }, 29 | "repository": "tc39/proposal-explicit-resource-management" 30 | } 31 | --------------------------------------------------------------------------------