From 20b3c31c5e344a8d2718cebe3abb89c4bf0a78b5 Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Sat, 8 Feb 2025 18:49:42 -0500 Subject: [PATCH] Iterator disposal --- .../guide/resource_management/index.md | 46 +++++++++++++++++-- .../symbol.asyncdispose/index.md | 30 +++++++++++- .../iterator/symbol.dispose/index.md | 32 ++++++++++++- files/sidebars/jssidebar.yaml | 2 + 4 files changed, 104 insertions(+), 6 deletions(-) diff --git a/files/en-us/web/javascript/guide/resource_management/index.md b/files/en-us/web/javascript/guide/resource_management/index.md index 7f73a6e3bca89da..92086abdc955df1 100644 --- a/files/en-us/web/javascript/guide/resource_management/index.md +++ b/files/en-us/web/javascript/guide/resource_management/index.md @@ -172,7 +172,7 @@ Some things to note: ## The `DisposableStack` and `AsyncDisposableStack` objects -`using` and `await using` are special syntaxes. Syntaxes are convenient and hides a lot of the complexity, but sometimes you need to do things manually. +`using` and `await using` are special syntaxes. Syntaxes are convenient and hide a lot of the complexity, but sometimes you need to do things manually. For one common example: what if you don't want to dispose the resource at the end of _this_ scope, but at some _later_ scope? Consider this: @@ -197,9 +197,49 @@ if (someCondition) { However, this means all logic has to be written inside the `if` or `else`, causing a lot of duplication. What we want to do is to acquire and register the resource in one scope but dispose it in another. We can use a {{jsxref("DisposableStack")}} for this purpose. -Another use case is when you have a resource that does not yet implement the disposable protocol, so it will be rejected by `using`. Yet another use case is when you have a disposal action to perform but it's not "tethered" to any resource in particular. Maybe you just want to log a message saying "All database connections closed" when there are multiple connections open simultaneously. +```js +{ + using stack = new DisposableStack(); + let reader; + if (someCondition) { + reader = stack.use(stream.getReader()); + } else { + reader = stack.use(stream.getReader({ mode: "byob" })); + } + // Do something with reader + // Before scope exit, stack is disposed, which disposes reader +} +``` -TODO +Another use case is when you have a resource that does not yet implement the disposable protocol, so it will be rejected by `using`. + +```js +{ + using stack = new DisposableStack(); + // Suppose reader does not have the [Symbol.dispose]() method, + // then it cannot be used with using. + // However, we can manually pass a disposer function to stack.adopt + const reader = stack.adopt(stream.getReader(), (reader) => + reader.releaseLock(), + ); + // Do something with reader + // Before scope exit, stack is disposed, which disposes reader +} +``` + +Yet another use case is when you have a disposal action to perform but it's not "tethered" to any resource in particular. Maybe you just want to log a message saying "All database connections closed" when there are multiple connections open simultaneously. + +```js +{ + using stack = new DisposableStack(); + stack.defer(() => console.log("All database connections closed")); + const connection1 = stack.use(openConnection()); + const connection2 = stack.use(openConnection()); + // Do something with connection1 and connection2 + // Before scope exit, stack is disposed, which first disposes connection1 + // and connection2 and then logs the message +} +``` ## Error handling diff --git a/files/en-us/web/javascript/reference/global_objects/asynciterator/symbol.asyncdispose/index.md b/files/en-us/web/javascript/reference/global_objects/asynciterator/symbol.asyncdispose/index.md index 20da7c5f4f86ecc..506e81d11579b80 100644 --- a/files/en-us/web/javascript/reference/global_objects/asynciterator/symbol.asyncdispose/index.md +++ b/files/en-us/web/javascript/reference/global_objects/asynciterator/symbol.asyncdispose/index.md @@ -25,7 +25,31 @@ None ({{jsxref("undefined")}}). ## Examples -### Disposing an async iterator +### Declaring an async iterator with `await using` + +The `Symbol.asyncDispose` method is intended to be automatically called in an `await using` declaration. This is useful if you have an async iterator that you manually iterate over by calling its `next()` method; if you iterate it with {{jsxref("Statements/for-await...of", "for await...of")}} or something similar, then error handling and cleanup is done automatically. + +```js +async function* generateNumbers() { + try { + yield 1; + yield 2; + yield 3; + } finally { + console.log("Cleaning up"); + } +} + +async function doSomething() { + await using numbers = generateNumbers(); + const res1 = await numbers.next(); + // Not iterating the rest of the numbers + // Before the function exists, the async iterator is disposed + // Logs "Cleaning up" +} + +doSomething(); +``` ## Specifications @@ -36,3 +60,7 @@ None ({{jsxref("undefined")}}). {{Compat}} ## See also + +- [JavaScript resource management](/en-US/docs/Web/JavaScript/Guide/Resource_management) +- {{jsxref("Symbol.asyncDispose")}} +- {{jsxref("Statements/await_using", "await using")}} diff --git a/files/en-us/web/javascript/reference/global_objects/iterator/symbol.dispose/index.md b/files/en-us/web/javascript/reference/global_objects/iterator/symbol.dispose/index.md index e5fc426b37c3361..b7134b569ed5a49 100644 --- a/files/en-us/web/javascript/reference/global_objects/iterator/symbol.dispose/index.md +++ b/files/en-us/web/javascript/reference/global_objects/iterator/symbol.dispose/index.md @@ -7,7 +7,7 @@ browser-compat: javascript.builtins.Iterator.@@dispose {{JSRef}} -The **`[Symbol.dispose]()`** method of {{jsxref("Iterator")}} instances implements the _disposable protocol_ and allows it to be disposed when used with {{jsxref("Statements/using", "using")}} or {{jsxref("Statements/await_using", "await using")}}. It calls the `return()` method of `this`, if it exists. +The **`[Symbol.dispose]()`** method of {{jsxref("Iterator")}} instances implements the _disposable protocol_ and allows it to be disposed when used with {{jsxref("Statements/using", "using")}}. It calls the `return()` method of `this`, if it exists. ## Syntax @@ -25,7 +25,31 @@ None ({{jsxref("undefined")}}). ## Examples -### Disposing an iterator +### Declaring an iterator with `using` + +The `Symbol.dispose` method is intended to be automatically called in a `using` declaration. This is useful if you have an iterator that you manually iterate over by calling its `next()` method; if you iterate it with {{jsxref("Statements/for...of", "for...of")}} or something similar, then error handling and cleanup is done automatically. + +```js +function* generateNumbers() { + try { + yield 1; + yield 2; + yield 3; + } finally { + console.log("Cleaning up"); + } +} + +function doSomething() { + using numbers = generateNumbers(); + const res1 = numbers.next(); + // Not iterating the rest of the numbers + // Before the function exists, the async iterator is disposed + // Logs "Cleaning up" +} + +doSomething(); +``` ## Specifications @@ -36,3 +60,7 @@ None ({{jsxref("undefined")}}). {{Compat}} ## See also + +- [JavaScript resource management](/en-US/docs/Web/JavaScript/Guide/Resource_management) +- {{jsxref("Symbol.dispose")}} +- {{jsxref("Statements/using", "using")}} diff --git a/files/sidebars/jssidebar.yaml b/files/sidebars/jssidebar.yaml index 4e89223f9b318c5..36baee3aacbfee3 100644 --- a/files/sidebars/jssidebar.yaml +++ b/files/sidebars/jssidebar.yaml @@ -50,6 +50,8 @@ sidebar: title: Guide_Typed_arrays - link: /Web/JavaScript/Guide/Iterators_and_generators title: Guide_Iterators_generators + - link: /Web/JavaScript/Guide/Resource_management + title: Guide_Resource_management - link: /Web/JavaScript/Guide/Internationalization title: Guide_Internationalization - link: /Web/JavaScript/Guide/Meta_programming