| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 6c6c900 commit 312476c
5 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -386,6 +386,110 @@ try { | |||
| 386 | 386 | } | |
| 387 | 387 | ``` | |
| 388 | 388 | ||
| 389 | + ### `asyncLocalStorage.withScope(store)` | ||
| 390 | + | ||
| 391 | + <!-- YAML | ||
| 392 | + added: REPLACEME | ||
| 393 | + --> | ||
| 394 | + | ||
| 395 | + > Stability: 1 - Experimental | ||
| 396 | + | ||
| 397 | + * `store` {any} | ||
| 398 | + * Returns: {RunScope} | ||
| 399 | + | ||
| 400 | + Creates a disposable scope that enters the given store and automatically | ||
| 401 | + restores the previous store value when the scope is disposed. This method is | ||
| 402 | + designed to work with JavaScript's explicit resource management (`using` syntax). | ||
| 403 | + | ||
| 404 | + Example: | ||
| 405 | + | ||
| 406 | + ```mjs | ||
| 407 | + import { AsyncLocalStorage } from 'node:async_hooks'; | ||
| 408 | + | ||
| 409 | + const asyncLocalStorage = new AsyncLocalStorage(); | ||
| 410 | + | ||
| 411 | + { | ||
| 412 | + using _ = asyncLocalStorage.withScope('my-store'); | ||
| 413 | + console.log(asyncLocalStorage.getStore()); // Prints: my-store | ||
| 414 | + } | ||
| 415 | + | ||
| 416 | + console.log(asyncLocalStorage.getStore()); // Prints: undefined | ||
| 417 | + ``` | ||
| 418 | + | ||
| 419 | + ```cjs | ||
| 420 | + const { AsyncLocalStorage } = require('node:async_hooks'); | ||
| 421 | + | ||
| 422 | + const asyncLocalStorage = new AsyncLocalStorage(); | ||
| 423 | + | ||
| 424 | + { | ||
| 425 | + using _ = asyncLocalStorage.withScope('my-store'); | ||
| 426 | + console.log(asyncLocalStorage.getStore()); // Prints: my-store | ||
| 427 | + } | ||
| 428 | + | ||
| 429 | + console.log(asyncLocalStorage.getStore()); // Prints: undefined | ||
| 430 | + ``` | ||
| 431 | + | ||
| 432 | + The `withScope()` method is particularly useful for managing context in | ||
| 433 | + synchronous code where you want to ensure the previous store value is restored | ||
| 434 | + when exiting a block, even if an error is thrown. | ||
| 435 | + | ||
| 436 | + ```mjs | ||
| 437 | + import { AsyncLocalStorage } from 'node:async_hooks'; | ||
| 438 | + | ||
| 439 | + const asyncLocalStorage = new AsyncLocalStorage(); | ||
| 440 | + | ||
| 441 | + try { | ||
| 442 | + using _ = asyncLocalStorage.withScope('my-store'); | ||
| 443 | + console.log(asyncLocalStorage.getStore()); // Prints: my-store | ||
| 444 | + throw new Error('test'); | ||
| 445 | + } catch (e) { | ||
| 446 | + // Store is automatically restored even after error | ||
| 447 | + console.log(asyncLocalStorage.getStore()); // Prints: undefined | ||
| 448 | + } | ||
| 449 | + ``` | ||
| 450 | + | ||
| 451 | + ```cjs | ||
| 452 | + const { AsyncLocalStorage } = require('node:async_hooks'); | ||
| 453 | + | ||
| 454 | + const asyncLocalStorage = new AsyncLocalStorage(); | ||
| 455 | + | ||
| 456 | + try { | ||
| 457 | + using _ = asyncLocalStorage.withScope('my-store'); | ||
| 458 | + console.log(asyncLocalStorage.getStore()); // Prints: my-store | ||
| 459 | + throw new Error('test'); | ||
| 460 | + } catch (e) { | ||
| 461 | + // Store is automatically restored even after error | ||
| 462 | + console.log(asyncLocalStorage.getStore()); // Prints: undefined | ||
| 463 | + } | ||
| 464 | + ``` | ||
| 465 | + | ||
| 466 | + **Important:** When using `withScope()` in async functions before the first | ||
| 467 | + `await`, be aware that the scope change will affect the caller's context. The | ||
| 468 | + synchronous portion of an async function (before the first `await`) runs | ||
| 469 | + immediately when called, and when it reaches the first `await`, it returns the | ||
| 470 | + promise to the caller. At that point, the scope change becomes visible in the | ||
| 471 | + caller's context and will persist in subsequent synchronous code until something | ||
| 472 | + else changes the scope value. For async operations, prefer using `run()` which | ||
| 473 | + properly isolates context across async boundaries. | ||
| 474 | + | ||
| 475 | + ```mjs | ||
| 476 | + import { AsyncLocalStorage } from 'node:async_hooks'; | ||
| 477 | + | ||
| 478 | + const asyncLocalStorage = new AsyncLocalStorage(); | ||
| 479 | + | ||
| 480 | + async function example() { | ||
| 481 | + using _ = asyncLocalStorage.withScope('my-store'); | ||
| 482 | + console.log(asyncLocalStorage.getStore()); // Prints: my-store | ||
| 483 | + await someAsyncOperation(); // Function pauses here and returns promise | ||
| 484 | + console.log(asyncLocalStorage.getStore()); // Prints: my-store | ||
| 485 | + } | ||
| 486 | + | ||
| 487 | + // Calling without await | ||
| 488 | + example(); // Synchronous portion runs, then pauses at first await | ||
| 489 | + // After the promise is returned, the scope 'my-store' is now active in caller! | ||
| 490 | + console.log(asyncLocalStorage.getStore()); // Prints: my-store (unexpected!) | ||
| 491 | + ``` | ||
| 492 | + | ||
| 389 | 493 | ### Usage with `async/await` | |
| 390 | 494 | ||
| 391 | 495 | If, within an async function, only one `await` call is to run within a context, | |
@@ -420,6 +524,64 @@ of `asyncLocalStorage.getStore()` after the calls you suspect are responsible | |||
| 420 | 524 | for the loss. When the code logs `undefined`, the last callback called is | |
| 421 | 525 | probably responsible for the context loss. | |
| 422 | 526 | ||
| 527 | + ## Class: `RunScope` | ||
| 528 | + | ||
| 529 | + <!-- YAML | ||
| 530 | + added: REPLACEME | ||
| 531 | + --> | ||
| 532 | + | ||
| 533 | + > Stability: 1 - Experimental | ||
| 534 | + | ||
| 535 | + A disposable scope returned by [`asyncLocalStorage.withScope()`][] that | ||
| 536 | + automatically restores the previous store value when disposed. This class | ||
| 537 | + implements the [Explicit Resource Management][] protocol and is designed to work | ||
| 538 | + with JavaScript's `using` syntax. | ||
| 539 | + | ||
| 540 | + The scope automatically restores the previous store value when the `using` block | ||
| 541 | + exits, whether through normal completion or by throwing an error. | ||
| 542 | + | ||
| 543 | + ### `scope.dispose()` | ||
| 544 | + | ||
| 545 | + <!-- YAML | ||
| 546 | + added: REPLACEME | ||
| 547 | + --> | ||
| 548 | + | ||
| 549 | + Explicitly ends the scope and restores the previous store value. This method | ||
| 550 | + is idempotent: calling it multiple times has the same effect as calling it once. | ||
| 551 | + | ||
| 552 | + The `[Symbol.dispose]()` method defers to `dispose()`. | ||
| 553 | + | ||
| 554 | + If `withScope()` is called without the `using` keyword, `dispose()` must be | ||
| 555 | + called manually to restore the previous store value. Forgetting to call | ||
| 556 | + `dispose()` will cause the store value to persist for the remainder of the | ||
| 557 | + current execution context: | ||
| 558 | + | ||
| 559 | + ```mjs | ||
| 560 | + import { AsyncLocalStorage } from 'node:async_hooks'; | ||
| 561 | + | ||
| 562 | + const storage = new AsyncLocalStorage(); | ||
| 563 | + | ||
| 564 | + // Without using, the scope must be disposed manually | ||
| 565 | + const scope = storage.withScope('my-store'); | ||
| 566 | + // storage.getStore() === 'my-store' here | ||
| 567 | + | ||
| 568 | + scope.dispose(); // Restore previous value | ||
| 569 | + // storage.getStore() === undefined here | ||
| 570 | + ``` | ||
| 571 | + | ||
| 572 | + ```cjs | ||
| 573 | + const { AsyncLocalStorage } = require('node:async_hooks'); | ||
| 574 | + | ||
| 575 | + const storage = new AsyncLocalStorage(); | ||
| 576 | + | ||
| 577 | + // Without using, the scope must be disposed manually | ||
| 578 | + const scope = storage.withScope('my-store'); | ||
| 579 | + // storage.getStore() === 'my-store' here | ||
| 580 | + | ||
| 581 | + scope.dispose(); // Restore previous value | ||
| 582 | + // storage.getStore() === undefined here | ||
| 583 | + ``` | ||
| 584 | + | ||
| 423 | 585 | ## Class: `AsyncResource` | |
| 424 | 586 | ||
| 425 | 587 | <!-- YAML | |
@@ -905,8 +1067,10 @@ const server = createServer((req, res) => { | |||
| 905 | 1067 | }).listen(3000); | |
| 906 | 1068 | ``` | |
| 907 | 1069 | ||
| 1070 | + [Explicit Resource Management]: https://github.com/tc39/proposal-explicit-resource-management | ||
| 908 | 1071 | [`AsyncResource`]: #class-asyncresource | |
| 909 | 1072 | [`EventEmitter`]: events.md#class-eventemitter | |
| 910 | 1073 | [`Stream`]: stream.md#stream | |
| 911 | 1074 | [`Worker`]: worker_threads.md#class-worker | |
| 1075 | + [`asyncLocalStorage.withScope()`]: #asynclocalstoragewithscopestore | ||
| 912 | 1076 | [`util.promisify()`]: util.md#utilpromisifyoriginal | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -12,6 +12,8 @@ const { | |||
| 12 | 12 | const AsyncContextFrame = require('internal/async_context_frame'); | |
| 13 | 13 | const { AsyncResource } = require('async_hooks'); | |
| 14 | 14 | ||
| 15 | + const RunScope = require('internal/async_local_storage/run_scope'); | ||
| 16 | + | ||
| 15 | 17 | class AsyncLocalStorage { | |
| 16 | 18 | #defaultValue = undefined; | |
| 17 | 19 | #name = undefined; | |
@@ -77,6 +79,10 @@ class AsyncLocalStorage { | |||
| 77 | 79 | } | |
| 78 | 80 | return frame?.get(this); | |
| 79 | 81 | } | |
| 82 | + | ||
| 83 | + withScope(store) { | ||
| 84 | + return new RunScope(this, store); | ||
| 85 | + } | ||
| 80 | 86 | } | |
| 81 | 87 | ||
| 82 | 88 | module.exports = AsyncLocalStorage; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -19,6 +19,8 @@ const { | |||
| 19 | 19 | executionAsyncResource, | |
| 20 | 20 | } = require('async_hooks'); | |
| 21 | 21 | ||
| 22 | + const RunScope = require('internal/async_local_storage/run_scope'); | ||
| 23 | + | ||
| 22 | 24 | const storageList = []; | |
| 23 | 25 | const storageHook = createHook({ | |
| 24 | 26 | init(asyncId, type, triggerAsyncId, resource) { | |
@@ -142,6 +144,10 @@ class AsyncLocalStorage { | |||
| 142 | 144 | } | |
| 143 | 145 | return this.#defaultValue; | |
| 144 | 146 | } | |
| 147 | + | ||
| 148 | + withScope(store) { | ||
| 149 | + return new RunScope(this, store); | ||
| 150 | + } | ||
| 145 | 151 | } | |
| 146 | 152 | ||
| 147 | 153 | module.exports = AsyncLocalStorage; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,31 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + const { | ||
| 4 | + SymbolDispose, | ||
| 5 | + } = primordials; | ||
| 6 | + | ||
| 7 | + class RunScope { | ||
| 8 | + #storage; | ||
| 9 | + #previousStore; | ||
| 10 | + #disposed = false; | ||
| 11 | + | ||
| 12 | + constructor(storage, store) { | ||
| 13 | + this.#storage = storage; | ||
| 14 | + this.#previousStore = storage.getStore(); | ||
| 15 | + storage.enterWith(store); | ||
| 16 | + } | ||
| 17 | + | ||
| 18 | + dispose() { | ||
| 19 | + if (this.#disposed) { | ||
| 20 | + return; | ||
| 21 | + } | ||
| 22 | + this.#disposed = true; | ||
| 23 | + this.#storage.enterWith(this.#previousStore); | ||
| 24 | + } | ||
| 25 | + | ||
| 26 | + [SymbolDispose]() { | ||
| 27 | + this.dispose(); | ||
| 28 | + } | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + module.exports = RunScope; | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments