From 09cb6ad7e0a9007abe9eb75814e5a112416c65b1 Mon Sep 17 00:00:00 2001 From: Stephen Belanger Date: Wed, 4 Feb 2026 19:03:40 +0800 Subject: [PATCH] async_hooks: add using scopes to AsyncLocalStorage Adds support for using scope = storage.withScope(data) to do the equivalent of a storage.run(data, fn) with using syntax. This enables avoiding unnecessary closures. --- doc/api/async_context.md | 95 ++++++++++ .../async_context_frame.js | 6 + .../async_local_storage/async_hooks.js | 6 + lib/internal/async_local_storage/run_scope.js | 27 +++ .../test-async-local-storage-run-scope.js | 165 ++++++++++++++++++ tools/doc/type-parser.mjs | 1 + 6 files changed, 300 insertions(+) create mode 100644 lib/internal/async_local_storage/run_scope.js create mode 100644 test/parallel/test-async-local-storage-run-scope.js diff --git a/doc/api/async_context.md b/doc/api/async_context.md index dd3b6a834ec950..a3d551f99269fe 100644 --- a/doc/api/async_context.md +++ b/doc/api/async_context.md @@ -386,6 +386,83 @@ try { } ``` +### `asyncLocalStorage.withScope(store)` + + + +> Stability: 1 - Experimental + +* `store` {any} +* Returns: {RunScope} + +Creates a disposable scope that enters the given store and automatically +restores the previous store value when the scope is disposed. This method is +designed to work with JavaScript's explicit resource management (`using` syntax). + +Example: + +```mjs +import { AsyncLocalStorage } from 'node:async_hooks'; + +const asyncLocalStorage = new AsyncLocalStorage(); + +{ + using scope = asyncLocalStorage.withScope('my-store'); + console.log(asyncLocalStorage.getStore()); // Prints: my-store +} + +console.log(asyncLocalStorage.getStore()); // Prints: undefined +``` + +```cjs +const { AsyncLocalStorage } = require('node:async_hooks'); + +const asyncLocalStorage = new AsyncLocalStorage(); + +{ + using scope = asyncLocalStorage.withScope('my-store'); + console.log(asyncLocalStorage.getStore()); // Prints: my-store +} + +console.log(asyncLocalStorage.getStore()); // Prints: undefined +``` + +The `withScope()` method is particularly useful for managing context in +synchronous code where you want to ensure the previous store value is restored +when exiting a block, even if an error is thrown. + +```mjs +import { AsyncLocalStorage } from 'node:async_hooks'; + +const asyncLocalStorage = new AsyncLocalStorage(); + +try { + using scope = asyncLocalStorage.withScope('my-store'); + console.log(asyncLocalStorage.getStore()); // Prints: my-store + throw new Error('test'); +} catch (e) { + // Store is automatically restored even after error + console.log(asyncLocalStorage.getStore()); // Prints: undefined +} +``` + +```cjs +const { AsyncLocalStorage } = require('node:async_hooks'); + +const asyncLocalStorage = new AsyncLocalStorage(); + +try { + using scope = asyncLocalStorage.withScope('my-store'); + console.log(asyncLocalStorage.getStore()); // Prints: my-store + throw new Error('test'); +} catch (e) { + // Store is automatically restored even after error + console.log(asyncLocalStorage.getStore()); // Prints: undefined +} +``` + ### Usage with `async/await` If, within an async function, only one `await` call is to run within a context, @@ -420,6 +497,22 @@ of `asyncLocalStorage.getStore()` after the calls you suspect are responsible for the loss. When the code logs `undefined`, the last callback called is probably responsible for the context loss. +## Class: `RunScope` + + + +> Stability: 1 - Experimental + +A disposable scope returned by [`asyncLocalStorage.withScope()`][] that +automatically restores the previous store value when disposed. This class +implements the [Explicit Resource Management][] protocol and is designed to work +with JavaScript's `using` syntax. + +The scope automatically restores the previous store value when the `using` block +exits, whether through normal completion or by throwing an error. + ## Class: `AsyncResource`