Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 164 additions & 0 deletions doc/api/async_context.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -386,6 +386,110 @@ try {
}
```

### `asyncLocalStorage.withScope(store)`

<!-- YAML
added: REPLACEME
-->

> 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 _ = 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 _ = 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 _ = 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 _ = 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
}
```

**Important:** When using `withScope()` in async functions before the first
`await`, be aware that the scope change will affect the caller's context. The
synchronous portion of an async function (before the first `await`) runs
immediately when called, and when it reaches the first `await`, it returns the
promise to the caller. At that point, the scope change becomes visible in the
caller's context and will persist in subsequent synchronous code until something
else changes the scope value. For async operations, prefer using `run()` which
properly isolates context across async boundaries.

```mjs
import { AsyncLocalStorage } from 'node:async_hooks';

const asyncLocalStorage = new AsyncLocalStorage();

async function example() {
using _ = asyncLocalStorage.withScope('my-store');
console.log(asyncLocalStorage.getStore()); // Prints: my-store
await someAsyncOperation(); // Function pauses here and returns promise
console.log(asyncLocalStorage.getStore()); // Prints: my-store
}

// Calling without await
example(); // Synchronous portion runs, then pauses at first await
// After the promise is returned, the scope 'my-store' is now active in caller!
console.log(asyncLocalStorage.getStore()); // Prints: my-store (unexpected!)
```

### Usage with `async/await`

If, within an async function, only one `await` call is to run within a context,
Expand DownExpand Up@@ -420,6 +524,64 @@ 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`

<!-- YAML
added: REPLACEME
-->

> 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.

### `scope.dispose()`

<!-- YAML
added: REPLACEME
-->

Explicitly ends the scope and restores the previous store value. This method
is idempotent: calling it multiple times has the same effect as calling it once.

The `[Symbol.dispose]()` method defers to `dispose()`.

If `withScope()` is called without the `using` keyword, `dispose()` must be
called manually to restore the previous store value. Forgetting to call
`dispose()` will cause the store value to persist for the remainder of the
current execution context:

```mjs
import { AsyncLocalStorage } from 'node:async_hooks';

const storage = new AsyncLocalStorage();

// Without using, the scope must be disposed manually
const scope = storage.withScope('my-store');
// storage.getStore() === 'my-store' here

scope.dispose(); // Restore previous value
// storage.getStore() === undefined here
```

```cjs
const { AsyncLocalStorage } = require('node:async_hooks');

const storage = new AsyncLocalStorage();

// Without using, the scope must be disposed manually
const scope = storage.withScope('my-store');
// storage.getStore() === 'my-store' here

scope.dispose(); // Restore previous value
// storage.getStore() === undefined here
```

## Class: `AsyncResource`

<!-- YAML
Expand DownExpand Up@@ -905,8 +1067,10 @@ const server = createServer((req, res) => {
}).listen(3000);
```

[Explicit Resource Management]: https://github.com/tc39/proposal-explicit-resource-management
[`AsyncResource`]: #class-asyncresource
[`EventEmitter`]: events.md#class-eventemitter
[`Stream`]: stream.md#stream
[`Worker`]: worker_threads.md#class-worker
[`asyncLocalStorage.withScope()`]: #asynclocalstoragewithscopestore
[`util.promisify()`]: util.md#utilpromisifyoriginal
6 changes: 6 additions & 0 deletions lib/internal/async_local_storage/async_context_frame.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,8 @@ const {
const AsyncContextFrame = require('internal/async_context_frame');
const { AsyncResource } = require('async_hooks');

const RunScope = require('internal/async_local_storage/run_scope');

class AsyncLocalStorage {
#defaultValue = undefined;
#name = undefined;
Expand DownExpand Up@@ -77,6 +79,10 @@ class AsyncLocalStorage {
}
return frame?.get(this);
}

withScope(store) {
return new RunScope(this, store);
}
}

module.exports = AsyncLocalStorage;
6 changes: 6 additions & 0 deletions lib/internal/async_local_storage/async_hooks.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,6 +24,8 @@ const {
executionAsyncResource,
}=require('async_hooks');

constRunScope=require('internal/async_local_storage/run_scope');

conststorageList=[];

functiongetOrCreateResourceStore(resource){
Expand DownExpand Up@@ -156,6 +158,10 @@ class AsyncLocalStorage {
}
returnthis.#defaultValue;
}

withScope(store){
returnnewRunScope(this,store);
}
}

module.exports=AsyncLocalStorage;
31 changes: 31 additions & 0 deletions lib/internal/async_local_storage/run_scope.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
'use strict';

const {
SymbolDispose,
} = primordials;

class RunScope {
#storage;
#previousStore;
#disposed = false;

constructor(storage, store) {
this.#storage = storage;
this.#previousStore = storage.getStore();
storage.enterWith(store);
}

dispose() {
if (this.#disposed) {
return;
}
this.#disposed = true;
this.#storage.enterWith(this.#previousStore);
}

[SymbolDispose]() {
this.dispose();
}
}

module.exports = RunScope;
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 164 additions & 0 deletions doc/api/async_context.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -386,6 +386,110 @@ try {
}
```

### `asyncLocalStorage.withScope(store)`

<!-- YAML
added: REPLACEME
-->

> 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 _ = 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 _ = 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 _ = 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 _ = 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
}
```

**Important:** When using `withScope()` in async functions before the first
`await`, be aware that the scope change will affect the caller's context. The
synchronous portion of an async function (before the first `await`) runs
immediately when called, and when it reaches the first `await`, it returns the
promise to the caller. At that point, the scope change becomes visible in the
caller's context and will persist in subsequent synchronous code until something
else changes the scope value. For async operations, prefer using `run()` which
properly isolates context across async boundaries.

```mjs
import { AsyncLocalStorage } from 'node:async_hooks';

const asyncLocalStorage = new AsyncLocalStorage();

async function example() {
using _ = asyncLocalStorage.withScope('my-store');
console.log(asyncLocalStorage.getStore()); // Prints: my-store
await someAsyncOperation(); // Function pauses here and returns promise
console.log(asyncLocalStorage.getStore()); // Prints: my-store
}

// Calling without await
example(); // Synchronous portion runs, then pauses at first await
// After the promise is returned, the scope 'my-store' is now active in caller!
console.log(asyncLocalStorage.getStore()); // Prints: my-store (unexpected!)
```

### Usage with `async/await`

If, within an async function, only one `await` call is to run within a context,
Expand DownExpand Up@@ -420,6 +524,64 @@ 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`

<!-- YAML
added: REPLACEME
-->

> 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.

### `scope.dispose()`

<!-- YAML
added: REPLACEME
-->

Explicitly ends the scope and restores the previous store value. This method
is idempotent: calling it multiple times has the same effect as calling it once.

The `[Symbol.dispose]()` method defers to `dispose()`.

If `withScope()` is called without the `using` keyword, `dispose()` must be
called manually to restore the previous store value. Forgetting to call
`dispose()` will cause the store value to persist for the remainder of the
current execution context:

```mjs
import { AsyncLocalStorage } from 'node:async_hooks';

const storage = new AsyncLocalStorage();

// Without using, the scope must be disposed manually
const scope = storage.withScope('my-store');
// storage.getStore() === 'my-store' here

scope.dispose(); // Restore previous value
// storage.getStore() === undefined here
```

```cjs
const { AsyncLocalStorage } = require('node:async_hooks');

const storage = new AsyncLocalStorage();

// Without using, the scope must be disposed manually
const scope = storage.withScope('my-store');
// storage.getStore() === 'my-store' here

scope.dispose(); // Restore previous value
// storage.getStore() === undefined here
```

## Class: `AsyncResource`

<!-- YAML
Expand DownExpand Up@@ -905,8 +1067,10 @@ const server = createServer((req, res) => {
}).listen(3000);
```

[Explicit Resource Management]: https://github.com/tc39/proposal-explicit-resource-management
[`AsyncResource`]: #class-asyncresource
[`EventEmitter`]: events.md#class-eventemitter
[`Stream`]: stream.md#stream
[`Worker`]: worker_threads.md#class-worker
[`asyncLocalStorage.withScope()`]: #asynclocalstoragewithscopestore
[`util.promisify()`]: util.md#utilpromisifyoriginal
6 changes: 6 additions & 0 deletions lib/internal/async_local_storage/async_context_frame.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,8 @@ const {
const AsyncContextFrame = require('internal/async_context_frame');
const { AsyncResource } = require('async_hooks');

const RunScope = require('internal/async_local_storage/run_scope');

class AsyncLocalStorage {
#defaultValue = undefined;
#name = undefined;
Expand DownExpand Up@@ -77,6 +79,10 @@ class AsyncLocalStorage {
}
return frame?.get(this);
}

withScope(store) {
return new RunScope(this, store);
}
}

module.exports = AsyncLocalStorage;
6 changes: 6 additions & 0 deletions lib/internal/async_local_storage/async_hooks.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,6 +24,8 @@ const {
executionAsyncResource,
}=require('async_hooks');

constRunScope=require('internal/async_local_storage/run_scope');

conststorageList=[];

functiongetOrCreateResourceStore(resource){
Expand DownExpand Up@@ -156,6 +158,10 @@ class AsyncLocalStorage {
}
returnthis.#defaultValue;
}

withScope(store){
returnnewRunScope(this,store);
}
}

module.exports=AsyncLocalStorage;
31 changes: 31 additions & 0 deletions lib/internal/async_local_storage/run_scope.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
'use strict';

const {
SymbolDispose,
} = primordials;

class RunScope {
#storage;
#previousStore;
#disposed = false;

constructor(storage, store) {
this.#storage = storage;
this.#previousStore = storage.getStore();
storage.enterWith(store);
}

dispose() {
if (this.#disposed) {
return;
}
this.#disposed = true;
this.#storage.enterWith(this.#previousStore);
}

[SymbolDispose]() {
this.dispose();
}
}

module.exports = RunScope;
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 164 additions & 0 deletions doc/api/async_context.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -386,6 +386,110 @@ try {
}
```

### `asyncLocalStorage.withScope(store)`

<!-- YAML
added: REPLACEME
-->

> 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 _ = 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 _ = 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 _ = 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 _ = 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
}
```

**Important:** When using `withScope()` in async functions before the first
`await`, be aware that the scope change will affect the caller's context. The
synchronous portion of an async function (before the first `await`) runs
immediately when called, and when it reaches the first `await`, it returns the
promise to the caller. At that point, the scope change becomes visible in the
caller's context and will persist in subsequent synchronous code until something
else changes the scope value. For async operations, prefer using `run()` which
properly isolates context across async boundaries.

```mjs
import { AsyncLocalStorage } from 'node:async_hooks';

const asyncLocalStorage = new AsyncLocalStorage();

async function example() {
using _ = asyncLocalStorage.withScope('my-store');
console.log(asyncLocalStorage.getStore()); // Prints: my-store
await someAsyncOperation(); // Function pauses here and returns promise
console.log(asyncLocalStorage.getStore()); // Prints: my-store
}

// Calling without await
example(); // Synchronous portion runs, then pauses at first await
// After the promise is returned, the scope 'my-store' is now active in caller!
console.log(asyncLocalStorage.getStore()); // Prints: my-store (unexpected!)
```

### Usage with `async/await`

If, within an async function, only one `await` call is to run within a context,
Expand DownExpand Up@@ -420,6 +524,64 @@ 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`

<!-- YAML
added: REPLACEME
-->

> 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.

### `scope.dispose()`

<!-- YAML
added: REPLACEME
-->

Explicitly ends the scope and restores the previous store value. This method
is idempotent: calling it multiple times has the same effect as calling it once.

The `[Symbol.dispose]()` method defers to `dispose()`.

If `withScope()` is called without the `using` keyword, `dispose()` must be
called manually to restore the previous store value. Forgetting to call
`dispose()` will cause the store value to persist for the remainder of the
current execution context:

```mjs
import { AsyncLocalStorage } from 'node:async_hooks';

const storage = new AsyncLocalStorage();

// Without using, the scope must be disposed manually
const scope = storage.withScope('my-store');
// storage.getStore() === 'my-store' here

scope.dispose(); // Restore previous value
// storage.getStore() === undefined here
```

```cjs
const { AsyncLocalStorage } = require('node:async_hooks');

const storage = new AsyncLocalStorage();

// Without using, the scope must be disposed manually
const scope = storage.withScope('my-store');
// storage.getStore() === 'my-store' here

scope.dispose(); // Restore previous value
// storage.getStore() === undefined here
```

## Class: `AsyncResource`

<!-- YAML
Expand DownExpand Up@@ -905,8 +1067,10 @@ const server = createServer((req, res) => {
}).listen(3000);
```

[Explicit Resource Management]: https://github.com/tc39/proposal-explicit-resource-management
[`AsyncResource`]: #class-asyncresource
[`EventEmitter`]: events.md#class-eventemitter
[`Stream`]: stream.md#stream
[`Worker`]: worker_threads.md#class-worker
[`asyncLocalStorage.withScope()`]: #asynclocalstoragewithscopestore
[`util.promisify()`]: util.md#utilpromisifyoriginal
6 changes: 6 additions & 0 deletions lib/internal/async_local_storage/async_context_frame.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,8 @@ const {
const AsyncContextFrame = require('internal/async_context_frame');
const { AsyncResource } = require('async_hooks');

const RunScope = require('internal/async_local_storage/run_scope');

class AsyncLocalStorage {
#defaultValue = undefined;
#name = undefined;
Expand DownExpand Up@@ -77,6 +79,10 @@ class AsyncLocalStorage {
}
return frame?.get(this);
}

withScope(store) {
return new RunScope(this, store);
}
}

module.exports = AsyncLocalStorage;
6 changes: 6 additions & 0 deletions lib/internal/async_local_storage/async_hooks.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,6 +24,8 @@ const {
executionAsyncResource,
}=require('async_hooks');

constRunScope=require('internal/async_local_storage/run_scope');

conststorageList=[];

functiongetOrCreateResourceStore(resource){
Expand DownExpand Up@@ -156,6 +158,10 @@ class AsyncLocalStorage {
}
returnthis.#defaultValue;
}

withScope(store){
returnnewRunScope(this,store);
}
}

module.exports=AsyncLocalStorage;
31 changes: 31 additions & 0 deletions lib/internal/async_local_storage/run_scope.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
'use strict';

const {
SymbolDispose,
} = primordials;

class RunScope {
#storage;
#previousStore;
#disposed = false;

constructor(storage, store) {
this.#storage = storage;
this.#previousStore = storage.getStore();
storage.enterWith(store);
}

dispose() {
if (this.#disposed) {
return;
}
this.#disposed = true;
this.#storage.enterWith(this.#previousStore);
}

[SymbolDispose]() {
this.dispose();
}
}

module.exports = RunScope;
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 164 additions & 0 deletions doc/api/async_context.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -386,6 +386,110 @@ try {
}
```

### `asyncLocalStorage.withScope(store)`

<!-- YAML
added: REPLACEME
-->

> 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 _ = 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 _ = 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 _ = 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 _ = 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
}
```

**Important:** When using `withScope()` in async functions before the first
`await`, be aware that the scope change will affect the caller's context. The
synchronous portion of an async function (before the first `await`) runs
immediately when called, and when it reaches the first `await`, it returns the
promise to the caller. At that point, the scope change becomes visible in the
caller's context and will persist in subsequent synchronous code until something
else changes the scope value. For async operations, prefer using `run()` which
properly isolates context across async boundaries.

```mjs
import { AsyncLocalStorage } from 'node:async_hooks';

const asyncLocalStorage = new AsyncLocalStorage();

async function example() {
using _ = asyncLocalStorage.withScope('my-store');
console.log(asyncLocalStorage.getStore()); // Prints: my-store
await someAsyncOperation(); // Function pauses here and returns promise
console.log(asyncLocalStorage.getStore()); // Prints: my-store
}

// Calling without await
example(); // Synchronous portion runs, then pauses at first await
// After the promise is returned, the scope 'my-store' is now active in caller!
console.log(asyncLocalStorage.getStore()); // Prints: my-store (unexpected!)
```

### Usage with `async/await`

If, within an async function, only one `await` call is to run within a context,
Expand DownExpand Up@@ -420,6 +524,64 @@ 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`

<!-- YAML
added: REPLACEME
-->

> 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.

### `scope.dispose()`

<!-- YAML
added: REPLACEME
-->

Explicitly ends the scope and restores the previous store value. This method
is idempotent: calling it multiple times has the same effect as calling it once.

The `[Symbol.dispose]()` method defers to `dispose()`.

If `withScope()` is called without the `using` keyword, `dispose()` must be
called manually to restore the previous store value. Forgetting to call
`dispose()` will cause the store value to persist for the remainder of the
current execution context:

```mjs
import { AsyncLocalStorage } from 'node:async_hooks';

const storage = new AsyncLocalStorage();

// Without using, the scope must be disposed manually
const scope = storage.withScope('my-store');
// storage.getStore() === 'my-store' here

scope.dispose(); // Restore previous value
// storage.getStore() === undefined here
```

```cjs
const { AsyncLocalStorage } = require('node:async_hooks');

const storage = new AsyncLocalStorage();

// Without using, the scope must be disposed manually
const scope = storage.withScope('my-store');
// storage.getStore() === 'my-store' here

scope.dispose(); // Restore previous value
// storage.getStore() === undefined here
```

## Class: `AsyncResource`

<!-- YAML
Expand DownExpand Up@@ -905,8 +1067,10 @@ const server = createServer((req, res) => {
}).listen(3000);
```

[Explicit Resource Management]: https://github.com/tc39/proposal-explicit-resource-management
[`AsyncResource`]: #class-asyncresource
[`EventEmitter`]: events.md#class-eventemitter
[`Stream`]: stream.md#stream
[`Worker`]: worker_threads.md#class-worker
[`asyncLocalStorage.withScope()`]: #asynclocalstoragewithscopestore
[`util.promisify()`]: util.md#utilpromisifyoriginal
6 changes: 6 additions & 0 deletions lib/internal/async_local_storage/async_context_frame.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,8 @@ const {
const AsyncContextFrame = require('internal/async_context_frame');
const { AsyncResource } = require('async_hooks');

const RunScope = require('internal/async_local_storage/run_scope');

class AsyncLocalStorage {
#defaultValue = undefined;
#name = undefined;
Expand DownExpand Up@@ -77,6 +79,10 @@ class AsyncLocalStorage {
}
return frame?.get(this);
}

withScope(store) {
return new RunScope(this, store);
}
}

module.exports = AsyncLocalStorage;
6 changes: 6 additions & 0 deletions lib/internal/async_local_storage/async_hooks.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,6 +24,8 @@ const {
executionAsyncResource,
}=require('async_hooks');

constRunScope=require('internal/async_local_storage/run_scope');

conststorageList=[];

functiongetOrCreateResourceStore(resource){
Expand DownExpand Up@@ -156,6 +158,10 @@ class AsyncLocalStorage {
}
returnthis.#defaultValue;
}

withScope(store){
returnnewRunScope(this,store);
}
}

module.exports=AsyncLocalStorage;
31 changes: 31 additions & 0 deletions lib/internal/async_local_storage/run_scope.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
'use strict';

const {
SymbolDispose,
} = primordials;

class RunScope {
#storage;
#previousStore;
#disposed = false;

constructor(storage, store) {
this.#storage = storage;
this.#previousStore = storage.getStore();
storage.enterWith(store);
}

dispose() {
if (this.#disposed) {
return;
}
this.#disposed = true;
this.#storage.enterWith(this.#previousStore);
}

[SymbolDispose]() {
this.dispose();
}
}

module.exports = RunScope;
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 164 additions & 0 deletions doc/api/async_context.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -386,6 +386,110 @@ try {
}
```

### `asyncLocalStorage.withScope(store)`

<!-- YAML
added: REPLACEME
-->

> 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 _ = 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 _ = 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 _ = 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 _ = 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
}
```

**Important:** When using `withScope()` in async functions before the first
`await`, be aware that the scope change will affect the caller's context. The
synchronous portion of an async function (before the first `await`) runs
immediately when called, and when it reaches the first `await`, it returns the
promise to the caller. At that point, the scope change becomes visible in the
caller's context and will persist in subsequent synchronous code until something
else changes the scope value. For async operations, prefer using `run()` which
properly isolates context across async boundaries.

```mjs
import { AsyncLocalStorage } from 'node:async_hooks';

const asyncLocalStorage = new AsyncLocalStorage();

async function example() {
using _ = asyncLocalStorage.withScope('my-store');
console.log(asyncLocalStorage.getStore()); // Prints: my-store
await someAsyncOperation(); // Function pauses here and returns promise
console.log(asyncLocalStorage.getStore()); // Prints: my-store
}

// Calling without await
example(); // Synchronous portion runs, then pauses at first await
// After the promise is returned, the scope 'my-store' is now active in caller!
console.log(asyncLocalStorage.getStore()); // Prints: my-store (unexpected!)
```

### Usage with `async/await`

If, within an async function, only one `await` call is to run within a context,
Expand DownExpand Up@@ -420,6 +524,64 @@ 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`

<!-- YAML
added: REPLACEME
-->

> 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.

### `scope.dispose()`

<!-- YAML
added: REPLACEME
-->

Explicitly ends the scope and restores the previous store value. This method
is idempotent: calling it multiple times has the same effect as calling it once.

The `[Symbol.dispose]()` method defers to `dispose()`.

If `withScope()` is called without the `using` keyword, `dispose()` must be
called manually to restore the previous store value. Forgetting to call
`dispose()` will cause the store value to persist for the remainder of the
current execution context:

```mjs
import { AsyncLocalStorage } from 'node:async_hooks';

const storage = new AsyncLocalStorage();

// Without using, the scope must be disposed manually
const scope = storage.withScope('my-store');
// storage.getStore() === 'my-store' here

scope.dispose(); // Restore previous value
// storage.getStore() === undefined here
```

```cjs
const { AsyncLocalStorage } = require('node:async_hooks');

const storage = new AsyncLocalStorage();

// Without using, the scope must be disposed manually
const scope = storage.withScope('my-store');
// storage.getStore() === 'my-store' here

scope.dispose(); // Restore previous value
// storage.getStore() === undefined here
```

## Class: `AsyncResource`

<!-- YAML
Expand DownExpand Up@@ -905,8 +1067,10 @@ const server = createServer((req, res) => {
}).listen(3000);
```

[Explicit Resource Management]: https://github.com/tc39/proposal-explicit-resource-management
[`AsyncResource`]: #class-asyncresource
[`EventEmitter`]: events.md#class-eventemitter
[`Stream`]: stream.md#stream
[`Worker`]: worker_threads.md#class-worker
[`asyncLocalStorage.withScope()`]: #asynclocalstoragewithscopestore
[`util.promisify()`]: util.md#utilpromisifyoriginal
6 changes: 6 additions & 0 deletions lib/internal/async_local_storage/async_context_frame.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,8 @@ const {
const AsyncContextFrame = require('internal/async_context_frame');
const { AsyncResource } = require('async_hooks');

const RunScope = require('internal/async_local_storage/run_scope');

class AsyncLocalStorage {
#defaultValue = undefined;
#name = undefined;
Expand DownExpand Up@@ -77,6 +79,10 @@ class AsyncLocalStorage {
}
return frame?.get(this);
}

withScope(store) {
return new RunScope(this, store);
}
}

module.exports = AsyncLocalStorage;
6 changes: 6 additions & 0 deletions lib/internal/async_local_storage/async_hooks.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,6 +24,8 @@ const {
executionAsyncResource,
}=require('async_hooks');

constRunScope=require('internal/async_local_storage/run_scope');

conststorageList=[];

functiongetOrCreateResourceStore(resource){
Expand DownExpand Up@@ -156,6 +158,10 @@ class AsyncLocalStorage {
}
returnthis.#defaultValue;
}

withScope(store){
returnnewRunScope(this,store);
}
}

module.exports=AsyncLocalStorage;
31 changes: 31 additions & 0 deletions lib/internal/async_local_storage/run_scope.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
'use strict';

const {
SymbolDispose,
} = primordials;

class RunScope {
#storage;
#previousStore;
#disposed = false;

constructor(storage, store) {
this.#storage = storage;
this.#previousStore = storage.getStore();
storage.enterWith(store);
}

dispose() {
if (this.#disposed) {
return;
}
this.#disposed = true;
this.#storage.enterWith(this.#previousStore);
}

[SymbolDispose]() {
this.dispose();
}
}

module.exports = RunScope;
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 164 additions & 0 deletions doc/api/async_context.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -386,6 +386,110 @@ try {
}
```

### `asyncLocalStorage.withScope(store)`

<!-- YAML
added: REPLACEME
-->

> 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 _ = 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 _ = 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 _ = 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 _ = 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
}
```

**Important:** When using `withScope()` in async functions before the first
`await`, be aware that the scope change will affect the caller's context. The
synchronous portion of an async function (before the first `await`) runs
immediately when called, and when it reaches the first `await`, it returns the
promise to the caller. At that point, the scope change becomes visible in the
caller's context and will persist in subsequent synchronous code until something
else changes the scope value. For async operations, prefer using `run()` which
properly isolates context across async boundaries.

```mjs
import { AsyncLocalStorage } from 'node:async_hooks';

const asyncLocalStorage = new AsyncLocalStorage();

async function example() {
using _ = asyncLocalStorage.withScope('my-store');
console.log(asyncLocalStorage.getStore()); // Prints: my-store
await someAsyncOperation(); // Function pauses here and returns promise
console.log(asyncLocalStorage.getStore()); // Prints: my-store
}

// Calling without await
example(); // Synchronous portion runs, then pauses at first await
// After the promise is returned, the scope 'my-store' is now active in caller!
console.log(asyncLocalStorage.getStore()); // Prints: my-store (unexpected!)
```

### Usage with `async/await`

If, within an async function, only one `await` call is to run within a context,
Expand DownExpand Up@@ -420,6 +524,64 @@ 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`

<!-- YAML
added: REPLACEME
-->

> 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.

### `scope.dispose()`

<!-- YAML
added: REPLACEME
-->

Explicitly ends the scope and restores the previous store value. This method
is idempotent: calling it multiple times has the same effect as calling it once.

The `[Symbol.dispose]()` method defers to `dispose()`.

If `withScope()` is called without the `using` keyword, `dispose()` must be
called manually to restore the previous store value. Forgetting to call
`dispose()` will cause the store value to persist for the remainder of the
current execution context:

```mjs
import { AsyncLocalStorage } from 'node:async_hooks';

const storage = new AsyncLocalStorage();

// Without using, the scope must be disposed manually
const scope = storage.withScope('my-store');
// storage.getStore() === 'my-store' here

scope.dispose(); // Restore previous value
// storage.getStore() === undefined here
```

```cjs
const { AsyncLocalStorage } = require('node:async_hooks');

const storage = new AsyncLocalStorage();

// Without using, the scope must be disposed manually
const scope = storage.withScope('my-store');
// storage.getStore() === 'my-store' here

scope.dispose(); // Restore previous value
// storage.getStore() === undefined here
```

## Class: `AsyncResource`

<!-- YAML
Expand DownExpand Up@@ -905,8 +1067,10 @@ const server = createServer((req, res) => {
}).listen(3000);
```

[Explicit Resource Management]: https://github.com/tc39/proposal-explicit-resource-management
[`AsyncResource`]: #class-asyncresource
[`EventEmitter`]: events.md#class-eventemitter
[`Stream`]: stream.md#stream
[`Worker`]: worker_threads.md#class-worker
[`asyncLocalStorage.withScope()`]: #asynclocalstoragewithscopestore
[`util.promisify()`]: util.md#utilpromisifyoriginal
6 changes: 6 additions & 0 deletions lib/internal/async_local_storage/async_context_frame.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,8 @@ const {
const AsyncContextFrame = require('internal/async_context_frame');
const { AsyncResource } = require('async_hooks');

const RunScope = require('internal/async_local_storage/run_scope');

class AsyncLocalStorage {
#defaultValue = undefined;
#name = undefined;
Expand DownExpand Up@@ -77,6 +79,10 @@ class AsyncLocalStorage {
}
return frame?.get(this);
}

withScope(store) {
return new RunScope(this, store);
}
}

module.exports = AsyncLocalStorage;
6 changes: 6 additions & 0 deletions lib/internal/async_local_storage/async_hooks.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,6 +24,8 @@ const {
executionAsyncResource,
}=require('async_hooks');

constRunScope=require('internal/async_local_storage/run_scope');

conststorageList=[];

functiongetOrCreateResourceStore(resource){
Expand DownExpand Up@@ -156,6 +158,10 @@ class AsyncLocalStorage {
}
returnthis.#defaultValue;
}

withScope(store){
returnnewRunScope(this,store);
}
}

module.exports=AsyncLocalStorage;
31 changes: 31 additions & 0 deletions lib/internal/async_local_storage/run_scope.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
'use strict';

const {
SymbolDispose,
} = primordials;

class RunScope {
#storage;
#previousStore;
#disposed = false;

constructor(storage, store) {
this.#storage = storage;
this.#previousStore = storage.getStore();
storage.enterWith(store);
}

dispose() {
if (this.#disposed) {
return;
}
this.#disposed = true;
this.#storage.enterWith(this.#previousStore);
}

[SymbolDispose]() {
this.dispose();
}
}

module.exports = RunScope;
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 164 additions & 0 deletions doc/api/async_context.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -386,6 +386,110 @@ try {
}
```

### `asyncLocalStorage.withScope(store)`

<!-- YAML
added: REPLACEME
-->

> 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 _ = 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 _ = 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 _ = 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 _ = 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
}
```

**Important:** When using `withScope()` in async functions before the first
`await`, be aware that the scope change will affect the caller's context. The
synchronous portion of an async function (before the first `await`) runs
immediately when called, and when it reaches the first `await`, it returns the
promise to the caller. At that point, the scope change becomes visible in the
caller's context and will persist in subsequent synchronous code until something
else changes the scope value. For async operations, prefer using `run()` which
properly isolates context across async boundaries.

```mjs
import { AsyncLocalStorage } from 'node:async_hooks';

const asyncLocalStorage = new AsyncLocalStorage();

async function example() {
using _ = asyncLocalStorage.withScope('my-store');
console.log(asyncLocalStorage.getStore()); // Prints: my-store
await someAsyncOperation(); // Function pauses here and returns promise
console.log(asyncLocalStorage.getStore()); // Prints: my-store
}

// Calling without await
example(); // Synchronous portion runs, then pauses at first await
// After the promise is returned, the scope 'my-store' is now active in caller!
console.log(asyncLocalStorage.getStore()); // Prints: my-store (unexpected!)
```

### Usage with `async/await`

If, within an async function, only one `await` call is to run within a context,
Expand DownExpand Up@@ -420,6 +524,64 @@ 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`

<!-- YAML
added: REPLACEME
-->

> 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.

### `scope.dispose()`

<!-- YAML
added: REPLACEME
-->

Explicitly ends the scope and restores the previous store value. This method
is idempotent: calling it multiple times has the same effect as calling it once.

The `[Symbol.dispose]()` method defers to `dispose()`.

If `withScope()` is called without the `using` keyword, `dispose()` must be
called manually to restore the previous store value. Forgetting to call
`dispose()` will cause the store value to persist for the remainder of the
current execution context:

```mjs
import { AsyncLocalStorage } from 'node:async_hooks';

const storage = new AsyncLocalStorage();

// Without using, the scope must be disposed manually
const scope = storage.withScope('my-store');
// storage.getStore() === 'my-store' here

scope.dispose(); // Restore previous value
// storage.getStore() === undefined here
```

```cjs
const { AsyncLocalStorage } = require('node:async_hooks');

const storage = new AsyncLocalStorage();

// Without using, the scope must be disposed manually
const scope = storage.withScope('my-store');
// storage.getStore() === 'my-store' here

scope.dispose(); // Restore previous value
// storage.getStore() === undefined here
```

## Class: `AsyncResource`

<!-- YAML
Expand DownExpand Up@@ -905,8 +1067,10 @@ const server = createServer((req, res) => {
}).listen(3000);
```

[Explicit Resource Management]: https://github.com/tc39/proposal-explicit-resource-management
[`AsyncResource`]: #class-asyncresource
[`EventEmitter`]: events.md#class-eventemitter
[`Stream`]: stream.md#stream
[`Worker`]: worker_threads.md#class-worker
[`asyncLocalStorage.withScope()`]: #asynclocalstoragewithscopestore
[`util.promisify()`]: util.md#utilpromisifyoriginal
6 changes: 6 additions & 0 deletions lib/internal/async_local_storage/async_context_frame.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,8 @@ const {
const AsyncContextFrame = require('internal/async_context_frame');
const { AsyncResource } = require('async_hooks');

const RunScope = require('internal/async_local_storage/run_scope');

class AsyncLocalStorage {
#defaultValue = undefined;
#name = undefined;
Expand DownExpand Up@@ -77,6 +79,10 @@ class AsyncLocalStorage {
}
return frame?.get(this);
}

withScope(store) {
return new RunScope(this, store);
}
}

module.exports = AsyncLocalStorage;
6 changes: 6 additions & 0 deletions lib/internal/async_local_storage/async_hooks.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,6 +24,8 @@ const {
executionAsyncResource,
}=require('async_hooks');

constRunScope=require('internal/async_local_storage/run_scope');

conststorageList=[];

functiongetOrCreateResourceStore(resource){
Expand DownExpand Up@@ -156,6 +158,10 @@ class AsyncLocalStorage {
}
returnthis.#defaultValue;
}

withScope(store){
returnnewRunScope(this,store);
}
}

module.exports=AsyncLocalStorage;
31 changes: 31 additions & 0 deletions lib/internal/async_local_storage/run_scope.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
'use strict';

const {
SymbolDispose,
} = primordials;

class RunScope {
#storage;
#previousStore;
#disposed = false;

constructor(storage, store) {
this.#storage = storage;
this.#previousStore = storage.getStore();
storage.enterWith(store);
}

dispose() {
if (this.#disposed) {
return;
}
this.#disposed = true;
this.#storage.enterWith(this.#previousStore);
}

[SymbolDispose]() {
this.dispose();
}
}

module.exports = RunScope;
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 164 additions & 0 deletions doc/api/async_context.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -386,6 +386,110 @@ try {
}
```

### `asyncLocalStorage.withScope(store)`

<!-- YAML
added: REPLACEME
-->

> 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 _ = 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 _ = 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 _ = 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 _ = 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
}
```

**Important:** When using `withScope()` in async functions before the first
`await`, be aware that the scope change will affect the caller's context. The
synchronous portion of an async function (before the first `await`) runs
immediately when called, and when it reaches the first `await`, it returns the
promise to the caller. At that point, the scope change becomes visible in the
caller's context and will persist in subsequent synchronous code until something
else changes the scope value. For async operations, prefer using `run()` which
properly isolates context across async boundaries.

```mjs
import { AsyncLocalStorage } from 'node:async_hooks';

const asyncLocalStorage = new AsyncLocalStorage();

async function example() {
using _ = asyncLocalStorage.withScope('my-store');
console.log(asyncLocalStorage.getStore()); // Prints: my-store
await someAsyncOperation(); // Function pauses here and returns promise
console.log(asyncLocalStorage.getStore()); // Prints: my-store
}

// Calling without await
example(); // Synchronous portion runs, then pauses at first await
// After the promise is returned, the scope 'my-store' is now active in caller!
console.log(asyncLocalStorage.getStore()); // Prints: my-store (unexpected!)
```

### Usage with `async/await`

If, within an async function, only one `await` call is to run within a context,
Expand DownExpand Up@@ -420,6 +524,64 @@ 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`

<!-- YAML
added: REPLACEME
-->

> 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.

### `scope.dispose()`

<!-- YAML
added: REPLACEME
-->

Explicitly ends the scope and restores the previous store value. This method
is idempotent: calling it multiple times has the same effect as calling it once.

The `[Symbol.dispose]()` method defers to `dispose()`.

If `withScope()` is called without the `using` keyword, `dispose()` must be
called manually to restore the previous store value. Forgetting to call
`dispose()` will cause the store value to persist for the remainder of the
current execution context:

```mjs
import { AsyncLocalStorage } from 'node:async_hooks';

const storage = new AsyncLocalStorage();

// Without using, the scope must be disposed manually
const scope = storage.withScope('my-store');
// storage.getStore() === 'my-store' here

scope.dispose(); // Restore previous value
// storage.getStore() === undefined here
```

```cjs
const { AsyncLocalStorage } = require('node:async_hooks');

const storage = new AsyncLocalStorage();

// Without using, the scope must be disposed manually
const scope = storage.withScope('my-store');
// storage.getStore() === 'my-store' here

scope.dispose(); // Restore previous value
// storage.getStore() === undefined here
```

## Class: `AsyncResource`

<!-- YAML
Expand DownExpand Up@@ -905,8 +1067,10 @@ const server = createServer((req, res) => {
}).listen(3000);
```

[Explicit Resource Management]: https://github.com/tc39/proposal-explicit-resource-management
[`AsyncResource`]: #class-asyncresource
[`EventEmitter`]: events.md#class-eventemitter
[`Stream`]: stream.md#stream
[`Worker`]: worker_threads.md#class-worker
[`asyncLocalStorage.withScope()`]: #asynclocalstoragewithscopestore
[`util.promisify()`]: util.md#utilpromisifyoriginal
6 changes: 6 additions & 0 deletions lib/internal/async_local_storage/async_context_frame.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,8 @@ const {
const AsyncContextFrame = require('internal/async_context_frame');
const { AsyncResource } = require('async_hooks');

const RunScope = require('internal/async_local_storage/run_scope');

class AsyncLocalStorage {
#defaultValue = undefined;
#name = undefined;
Expand DownExpand Up@@ -77,6 +79,10 @@ class AsyncLocalStorage {
}
return frame?.get(this);
}

withScope(store) {
return new RunScope(this, store);
}
}

module.exports = AsyncLocalStorage;
6 changes: 6 additions & 0 deletions lib/internal/async_local_storage/async_hooks.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,6 +24,8 @@ const {
executionAsyncResource,
}=require('async_hooks');

constRunScope=require('internal/async_local_storage/run_scope');

conststorageList=[];

functiongetOrCreateResourceStore(resource){
Expand DownExpand Up@@ -156,6 +158,10 @@ class AsyncLocalStorage {
}
returnthis.#defaultValue;
}

withScope(store){
returnnewRunScope(this,store);
}
}

module.exports=AsyncLocalStorage;
31 changes: 31 additions & 0 deletions lib/internal/async_local_storage/run_scope.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
'use strict';

const {
SymbolDispose,
} = primordials;

class RunScope {
#storage;
#previousStore;
#disposed = false;

constructor(storage, store) {
this.#storage = storage;
this.#previousStore = storage.getStore();
storage.enterWith(store);
}

dispose() {
if (this.#disposed) {
return;
}
this.#disposed = true;
this.#storage.enterWith(this.#previousStore);
}

[SymbolDispose]() {
this.dispose();
}
}

module.exports = RunScope;
Loading