Skip to content

Commit b0bc07a

Browse files
committed
async_hooks: add use() method to AsyncLocalStorage
This provides a way to use the `using` syntax (when available) to manage AsyncLocalStorage contexts, as an alternative to `run()`.
1 parent fc054bb commit b0bc07a

File tree

4 files changed

+111
-0
lines changed

4 files changed

+111
-0
lines changed

doc/api/async_context.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,43 @@ try {
347347
}
348348
```
349349

350+
### `asyncLocalStorage.use(store)`
351+
352+
<!-- YAML
353+
added: REPLACEME
354+
-->
355+
356+
* `store` {any}
357+
* Returns: {Disposable} A disposable object.
358+
359+
Transitions into the given context, and transitions back into the previous
360+
context when the returned disposable object is disposed. The store is
361+
accessible to any asynchronous operations created before disposal.
362+
363+
Example:
364+
365+
```js
366+
const store1 = { id: 1 };
367+
const store2 = { id: 2 };
368+
369+
function inner() {
370+
// Once `using` syntax is supported, you can use that here, and omit the
371+
// dispose call at the end of this function.
372+
const disposable = asyncLocalStorage.use(store);
373+
asyncLocalStorage.getStore(); // Returns store2
374+
setTimeout(() => {
375+
asyncLocalStorage.getStore(); // Returns store2
376+
}, 200);
377+
disposable[Symbol.dispose]();
378+
}
379+
380+
asyncLocalStorage.run(store1, () => {
381+
asyncLocalStorage.getStore(); // Returns store1
382+
inner();
383+
asyncLocalStorage.getStore(); // Returns store1
384+
});
385+
```
386+
350387
### `asyncLocalStorage.exit(callback[, ...args])`
351388

352389
<!-- YAML

lib/internal/async_local_storage/async_context_frame.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const {
44
ReflectApply,
5+
SymbolDispose,
56
} = primordials;
67

78
const {
@@ -11,6 +12,19 @@ const {
1112
const AsyncContextFrame = require('internal/async_context_frame');
1213
const { AsyncResource } = require('async_hooks');
1314

15+
class DisposableStore {
16+
#oldFrame = undefined;
17+
18+
constructor(store, storage) {
19+
this.#oldFrame = AsyncContextFrame.current();
20+
storage.enterWith(store);
21+
}
22+
23+
[SymbolDispose]() {
24+
AsyncContextFrame.set(this.#oldFrame);
25+
}
26+
}
27+
1428
class AsyncLocalStorage {
1529
#defaultValue = undefined;
1630
#name = undefined;
@@ -62,6 +76,10 @@ class AsyncLocalStorage {
6276
}
6377
}
6478

79+
use(data) {
80+
return new DisposableStore(data, this);
81+
}
82+
6583
exit(fn, ...args) {
6684
return this.run(undefined, fn, ...args);
6785
}

lib/internal/async_local_storage/async_hooks.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const {
77
ObjectIs,
88
ReflectApply,
99
Symbol,
10+
SymbolDispose,
1011
} = primordials;
1112

1213
const {
@@ -30,6 +31,31 @@ const storageHook = createHook({
3031
},
3132
});
3233

34+
class DisposableStore {
35+
#oldStore = undefined;
36+
#resource = undefined;
37+
#kResourceStore = undefined;
38+
39+
constructor(store, storage) {
40+
this.#oldStore = storage.getStore();
41+
42+
if (ObjectIs(store, this.#oldStore)) {
43+
return;
44+
}
45+
46+
this.#kResourceStore = storage.kResourceStore;
47+
this.#resource = executionAsyncResource();
48+
this.#resource[this.#kResourceStore] = store;
49+
}
50+
51+
[SymbolDispose]() {
52+
if (this.#resource === undefined) {
53+
return;
54+
}
55+
this.#resource[this.#kResourceStore] = this.#oldStore;
56+
}
57+
}
58+
3359
class AsyncLocalStorage {
3460
#defaultValue = undefined;
3561
#name = undefined;
@@ -120,6 +146,11 @@ class AsyncLocalStorage {
120146
}
121147
}
122148

149+
use(store) {
150+
this._enable();
151+
return new DisposableStore(store, this);
152+
}
153+
123154
exit(callback, ...args) {
124155
if (!this.enabled) {
125156
return ReflectApply(callback, null, args);
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
'use strict';
2+
3+
require('../common');
4+
const assert = require('assert');
5+
const { AsyncLocalStorage } = require('async_hooks');
6+
7+
const storage = new AsyncLocalStorage();
8+
9+
const store1 = {};
10+
const store2 = {};
11+
const store3 = {};
12+
13+
function inner() {
14+
// TODO(bengl): Once `using` is supported use that here and don't call
15+
// dispose manually later.
16+
const disposable = storage.use(store2);
17+
assert.strictEqual(storage.getStore(), store2);
18+
storage.enterWith(store3);
19+
disposable[Symbol.dispose]();
20+
}
21+
22+
storage.enterWith(store1);
23+
assert.strictEqual(storage.getStore(), store1);
24+
inner();
25+
assert.strictEqual(storage.getStore(), store1);

0 commit comments

Comments
 (0)