Skip to content

Commit 877ab97

Browse files
vdeturckheimcodebytere
authored andcommittedFeb 27, 2020
async_hooks: introduce async-context API
Adding AsyncLocalStorage class to async_hooks module. This API provide a simple CLS-like set of features. Co-authored-by: Andrey Pechkurov <apechkurov@gmail.com> PR-URL: #26540 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Stephen Belanger <admin@stephenbelanger.com> Reviewed-By: Gireesh Punathil <gpunathi@in.ibm.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com>
1 parent 4dffd04 commit 877ab97

13 files changed

+667
-3
lines changed
 

‎benchmark/async_hooks/async-resource-vs-destroy.js

+34-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ const common = require('../common.js');
88
const {
99
createHook,
1010
executionAsyncResource,
11-
executionAsyncId
11+
executionAsyncId,
12+
AsyncLocalStorage
1213
} = require('async_hooks');
1314
const { createServer } = require('http');
1415

@@ -18,7 +19,7 @@ const connections = 500;
1819
const path = '/';
1920

2021
const bench = common.createBenchmark(main, {
21-
type: ['async-resource', 'destroy'],
22+
type: ['async-resource', 'destroy', 'async-local-storage'],
2223
asyncMethod: ['callbacks', 'async'],
2324
n: [1e6]
2425
});
@@ -102,6 +103,35 @@ function buildDestroy(getServe) {
102103
}
103104
}
104105

106+
function buildAsyncLocalStorage(getServe) {
107+
const asyncLocalStorage = new AsyncLocalStorage();
108+
const server = createServer((req, res) => {
109+
asyncLocalStorage.runSyncAndReturn(() => {
110+
getServe(getCLS, setCLS)(req, res);
111+
});
112+
});
113+
114+
return {
115+
server,
116+
close
117+
};
118+
119+
function getCLS() {
120+
const store = asyncLocalStorage.getStore();
121+
return store.get('store');
122+
}
123+
124+
function setCLS(state) {
125+
const store = asyncLocalStorage.getStore();
126+
store.set('store', state);
127+
}
128+
129+
function close() {
130+
asyncLocalStorage.disable();
131+
server.close();
132+
}
133+
}
134+
105135
function getServeAwait(getCLS, setCLS) {
106136
return async function serve(req, res) {
107137
setCLS(Math.random());
@@ -126,7 +156,8 @@ function getServeCallbacks(getCLS, setCLS) {
126156

127157
const types = {
128158
'async-resource': buildCurrentResource,
129-
'destroy': buildDestroy
159+
'destroy': buildDestroy,
160+
'async-local-storage': buildAsyncLocalStorage
130161
};
131162

132163
const asyncMethods = {

‎doc/api/async_hooks.md

+287
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,293 @@ for (let i = 0; i < 10; i++) {
859859
}
860860
```
861861

862+
## Class: `AsyncLocalStorage`
863+
<!-- YAML
864+
added: REPLACEME
865+
-->
866+
867+
This class is used to create asynchronous state within callbacks and promise
868+
chains. It allows storing data throughout the lifetime of a web request
869+
or any other asynchronous duration. It is similar to thread-local storage
870+
in other languages.
871+
872+
The following example builds a logger that will always know the current HTTP
873+
request and uses it to display enhanced logs without needing to explicitly
874+
provide the current HTTP request to it.
875+
876+
```js
877+
const { AsyncLocalStorage } = require('async_hooks');
878+
const http = require('http');
879+
880+
const kReq = 'CURRENT_REQUEST';
881+
const asyncLocalStorage = new AsyncLocalStorage();
882+
883+
function log(...args) {
884+
const store = asyncLocalStorage.getStore();
885+
// Make sure the store exists and it contains a request.
886+
if (store && store.has(kReq)) {
887+
const req = store.get(kReq);
888+
// Prints `GET /items ERR could not do something
889+
console.log(req.method, req.url, ...args);
890+
} else {
891+
console.log(...args);
892+
}
893+
}
894+
895+
http.createServer((request, response) => {
896+
asyncLocalStorage.run(() => {
897+
const store = asyncLocalStorage.getStore();
898+
store.set(kReq, request);
899+
someAsyncOperation((err, result) => {
900+
if (err) {
901+
log('ERR', err.message);
902+
}
903+
});
904+
});
905+
})
906+
.listen(8080);
907+
```
908+
909+
When having multiple instances of `AsyncLocalStorage`, they are independent
910+
from each other. It is safe to instantiate this class multiple times.
911+
912+
### `new AsyncLocalStorage()`
913+
<!-- YAML
914+
added: REPLACEME
915+
-->
916+
917+
Creates a new instance of `AsyncLocalStorage`. Store is only provided within a
918+
`run` or a `runSyncAndReturn` method call.
919+
920+
### `asyncLocalStorage.disable()`
921+
<!-- YAML
922+
added: REPLACEME
923+
-->
924+
925+
This method disables the instance of `AsyncLocalStorage`. All subsequent calls
926+
to `asyncLocalStorage.getStore()` will return `undefined` until
927+
`asyncLocalStorage.run()` or `asyncLocalStorage.runSyncAndReturn()`
928+
is called again.
929+
930+
When calling `asyncLocalStorage.disable()`, all current contexts linked to the
931+
instance will be exited.
932+
933+
Calling `asyncLocalStorage.disable()` is required before the
934+
`asyncLocalStorage` can be garbage collected. This does not apply to stores
935+
provided by the `asyncLocalStorage`, as those objects are garbage collected
936+
along with the corresponding async resources.
937+
938+
This method is to be used when the `asyncLocalStorage` is not in use anymore
939+
in the current process.
940+
941+
### `asyncLocalStorage.getStore()`
942+
<!-- YAML
943+
added: REPLACEME
944+
-->
945+
946+
* Returns: {Map}
947+
948+
This method returns the current store.
949+
If this method is called outside of an asynchronous context initialized by
950+
calling `asyncLocalStorage.run` or `asyncLocalStorage.runAndReturn`, it will
951+
return `undefined`.
952+
953+
### `asyncLocalStorage.run(callback[, ...args])`
954+
<!-- YAML
955+
added: REPLACEME
956+
-->
957+
958+
* `callback` {Function}
959+
* `...args` {any}
960+
961+
Calling `asyncLocalStorage.run(callback)` will create a new asynchronous
962+
context.
963+
Within the callback function and the asynchronous operations from the callback,
964+
`asyncLocalStorage.getStore()` will return an instance of `Map` known as
965+
"the store". This store will be persistent through the following
966+
asynchronous calls.
967+
968+
The callback will be ran asynchronously. Optionally, arguments can be passed
969+
to the function. They will be passed to the callback function.
970+
971+
If an error is thrown by the callback function, it will not be caught by
972+
a `try/catch` block as the callback is ran in a new asynchronous resource.
973+
Also, the stacktrace will be impacted by the asynchronous call.
974+
975+
Example:
976+
977+
```js
978+
asyncLocalStorage.run(() => {
979+
asyncLocalStorage.getStore(); // Returns a Map
980+
someAsyncOperation(() => {
981+
asyncLocalStorage.getStore(); // Returns the same Map
982+
});
983+
});
984+
asyncLocalStorage.getStore(); // Returns undefined
985+
```
986+
987+
### `asyncLocalStorage.exit(callback[, ...args])`
988+
<!-- YAML
989+
added: REPLACEME
990+
-->
991+
992+
* `callback` {Function}
993+
* `...args` {any}
994+
995+
Calling `asyncLocalStorage.exit(callback)` will create a new asynchronous
996+
context.
997+
Within the callback function and the asynchronous operations from the callback,
998+
`asyncLocalStorage.getStore()` will return `undefined`.
999+
1000+
The callback will be ran asynchronously. Optionally, arguments can be passed
1001+
to the function. They will be passed to the callback function.
1002+
1003+
If an error is thrown by the callback function, it will not be caught by
1004+
a `try/catch` block as the callback is ran in a new asynchronous resource.
1005+
Also, the stacktrace will be impacted by the asynchronous call.
1006+
1007+
Example:
1008+
1009+
```js
1010+
asyncLocalStorage.run(() => {
1011+
asyncLocalStorage.getStore(); // Returns a Map
1012+
asyncLocalStorage.exit(() => {
1013+
asyncLocalStorage.getStore(); // Returns undefined
1014+
});
1015+
asyncLocalStorage.getStore(); // Returns the same Map
1016+
});
1017+
```
1018+
1019+
### `asyncLocalStorage.runSyncAndReturn(callback[, ...args])`
1020+
<!-- YAML
1021+
added: REPLACEME
1022+
-->
1023+
1024+
* `callback` {Function}
1025+
* `...args` {any}
1026+
1027+
This methods runs a function synchronously within a context and return its
1028+
return value. The store is not accessible outside of the callback function or
1029+
the asynchronous operations created within the callback.
1030+
1031+
Optionally, arguments can be passed to the function. They will be passed to
1032+
the callback function.
1033+
1034+
If the callback function throws an error, it will be thrown by
1035+
`runSyncAndReturn` too. The stacktrace will not be impacted by this call and
1036+
the context will be exited.
1037+
1038+
Example:
1039+
1040+
```js
1041+
try {
1042+
asyncLocalStorage.runSyncAndReturn(() => {
1043+
asyncLocalStorage.getStore(); // Returns a Map
1044+
throw new Error();
1045+
});
1046+
} catch (e) {
1047+
asyncLocalStorage.getStore(); // Returns undefined
1048+
// The error will be caught here
1049+
}
1050+
```
1051+
1052+
### `asyncLocalStorage.exitSyncAndReturn(callback[, ...args])`
1053+
<!-- YAML
1054+
added: REPLACEME
1055+
-->
1056+
1057+
* `callback` {Function}
1058+
* `...args` {any}
1059+
1060+
This methods runs a function synchronously outside of a context and return its
1061+
return value. The store is not accessible within the callback function or
1062+
the asynchronous operations created within the callback.
1063+
1064+
Optionally, arguments can be passed to the function. They will be passed to
1065+
the callback function.
1066+
1067+
If the callback function throws an error, it will be thrown by
1068+
`exitSyncAndReturn` too. The stacktrace will not be impacted by this call and
1069+
the context will be re-entered.
1070+
1071+
Example:
1072+
1073+
```js
1074+
// Within a call to run or runSyncAndReturn
1075+
try {
1076+
asyncLocalStorage.getStore(); // Returns a Map
1077+
asyncLocalStorage.exitSyncAndReturn(() => {
1078+
asyncLocalStorage.getStore(); // Returns undefined
1079+
throw new Error();
1080+
});
1081+
} catch (e) {
1082+
asyncLocalStorage.getStore(); // Returns the same Map
1083+
// The error will be caught here
1084+
}
1085+
```
1086+
1087+
### Choosing between `run` and `runSyncAndReturn`
1088+
1089+
#### When to choose `run`
1090+
1091+
`run` is asynchronous. It is called with a callback function that
1092+
runs within a new asynchronous call. This is the most explicit behavior as
1093+
everything that is executed within the callback of `run` (including further
1094+
asynchronous operations) will have access to the store.
1095+
1096+
If an instance of `AsyncLocalStorage` is used for error management (for
1097+
instance, with `process.setUncaughtExceptionCaptureCallback`), only
1098+
exceptions thrown in the scope of the callback function will be associated
1099+
with the context.
1100+
1101+
This method is the safest as it provides strong scoping and consistent
1102+
behavior.
1103+
1104+
It cannot be promisified using `util.promisify`. If needed, the `Promise`
1105+
constructor can be used:
1106+
1107+
```js
1108+
new Promise((resolve, reject) => {
1109+
asyncLocalStorage.run(() => {
1110+
someFunction((err, result) => {
1111+
if (err) {
1112+
return reject(err);
1113+
}
1114+
return resolve(result);
1115+
});
1116+
});
1117+
});
1118+
```
1119+
1120+
#### When to choose `runSyncAndReturn`
1121+
1122+
`runSyncAndReturn` is synchronous. The callback function will be executed
1123+
synchronously and its return value will be returned by `runSyncAndReturn`.
1124+
The store will only be accessible from within the callback
1125+
function and the asynchronous operations created within this scope.
1126+
If the callback throws an error, `runSyncAndReturn` will throw it and it will
1127+
not be associated with the context.
1128+
1129+
This method provides good scoping while being synchronous.
1130+
1131+
#### Usage with `async/await`
1132+
1133+
If, within an async function, only one `await` call is to run within a context,
1134+
the following pattern should be used:
1135+
1136+
```js
1137+
async function fn() {
1138+
await asyncLocalStorage.runSyncAndReturn(() => {
1139+
asyncLocalStorage.getStore().set('key', value);
1140+
return foo(); // The return value of foo will be awaited
1141+
});
1142+
}
1143+
```
1144+
1145+
In this example, the store is only available in the callback function and the
1146+
functions called by `foo`. Outside of `runSyncAndReturn`, calling `getStore`
1147+
will return `undefined`.
1148+
8621149
[`after` callback]: #async_hooks_after_asyncid
8631150
[`before` callback]: #async_hooks_before_asyncid
8641151
[`destroy` callback]: #async_hooks_destroy_asyncid

‎lib/async_hooks.js

+93
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
'use strict';
22

33
const {
4+
Map,
45
NumberIsSafeInteger,
56
ReflectApply,
67
Symbol,
8+
79
} = primordials;
810

911
const {
@@ -209,11 +211,102 @@ class AsyncResource {
209211
}
210212
}
211213

214+
const storageList = [];
215+
const storageHook = createHook({
216+
init(asyncId, type, triggerAsyncId, resource) {
217+
const currentResource = executionAsyncResource();
218+
// Value of currentResource is always a non null object
219+
for (let i = 0; i < storageList.length; ++i) {
220+
storageList[i]._propagate(resource, currentResource);
221+
}
222+
}
223+
});
224+
225+
class AsyncLocalStorage {
226+
constructor() {
227+
this.kResourceStore = Symbol('kResourceStore');
228+
this.enabled = false;
229+
}
230+
231+
disable() {
232+
if (this.enabled) {
233+
this.enabled = false;
234+
// If this.enabled, the instance must be in storageList
235+
storageList.splice(storageList.indexOf(this), 1);
236+
if (storageList.length === 0) {
237+
storageHook.disable();
238+
}
239+
}
240+
}
241+
242+
// Propagate the context from a parent resource to a child one
243+
_propagate(resource, triggerResource) {
244+
const store = triggerResource[this.kResourceStore];
245+
if (this.enabled) {
246+
resource[this.kResourceStore] = store;
247+
}
248+
}
249+
250+
_enter() {
251+
if (!this.enabled) {
252+
this.enabled = true;
253+
storageList.push(this);
254+
storageHook.enable();
255+
}
256+
const resource = executionAsyncResource();
257+
resource[this.kResourceStore] = new Map();
258+
}
259+
260+
_exit() {
261+
const resource = executionAsyncResource();
262+
if (resource) {
263+
resource[this.kResourceStore] = undefined;
264+
}
265+
}
266+
267+
runSyncAndReturn(callback, ...args) {
268+
this._enter();
269+
try {
270+
return callback(...args);
271+
} finally {
272+
this._exit();
273+
}
274+
}
275+
276+
exitSyncAndReturn(callback, ...args) {
277+
this.enabled = false;
278+
try {
279+
return callback(...args);
280+
} finally {
281+
this.enabled = true;
282+
}
283+
}
284+
285+
getStore() {
286+
const resource = executionAsyncResource();
287+
if (this.enabled) {
288+
return resource[this.kResourceStore];
289+
}
290+
}
291+
292+
run(callback, ...args) {
293+
this._enter();
294+
process.nextTick(callback, ...args);
295+
this._exit();
296+
}
297+
298+
exit(callback, ...args) {
299+
this.enabled = false;
300+
process.nextTick(callback, ...args);
301+
this.enabled = true;
302+
}
303+
}
212304

213305
// Placing all exports down here because the exported classes won't export
214306
// otherwise.
215307
module.exports = {
216308
// Public API
309+
AsyncLocalStorage,
217310
createHook,
218311
executionAsyncId,
219312
triggerAsyncId,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use strict';
2+
require('../common');
3+
const assert = require('assert');
4+
const { AsyncLocalStorage } = require('async_hooks');
5+
6+
const asyncLocalStorage = new AsyncLocalStorage();
7+
8+
asyncLocalStorage.run((runArg) => {
9+
assert.strictEqual(runArg, 1);
10+
asyncLocalStorage.exit((exitArg) => {
11+
assert.strictEqual(exitArg, 2);
12+
}, 2);
13+
}, 1);
14+
15+
asyncLocalStorage.runSyncAndReturn((runArg) => {
16+
assert.strictEqual(runArg, 'foo');
17+
asyncLocalStorage.exitSyncAndReturn((exitArg) => {
18+
assert.strictEqual(exitArg, 'bar');
19+
}, 'bar');
20+
}, 'foo');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use strict';
2+
require('../common');
3+
const assert = require('assert');
4+
const { AsyncLocalStorage } = require('async_hooks');
5+
6+
const asyncLocalStorage = new AsyncLocalStorage();
7+
8+
async function test() {
9+
asyncLocalStorage.getStore().set('foo', 'bar');
10+
await Promise.resolve();
11+
assert.strictEqual(asyncLocalStorage.getStore().get('foo'), 'bar');
12+
}
13+
14+
async function main() {
15+
await asyncLocalStorage.runSyncAndReturn(test);
16+
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
17+
}
18+
19+
main();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
'use strict';
2+
require('../common');
3+
const assert = require('assert');
4+
const { AsyncLocalStorage } = require('async_hooks');
5+
6+
async function foo() {}
7+
8+
const asyncLocalStorage = new AsyncLocalStorage();
9+
10+
async function testOut() {
11+
await foo();
12+
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
13+
}
14+
15+
async function testAwait() {
16+
await foo();
17+
assert.notStrictEqual(asyncLocalStorage.getStore(), undefined);
18+
assert.strictEqual(asyncLocalStorage.getStore().get('key'), 'value');
19+
await asyncLocalStorage.exitSyncAndReturn(testOut);
20+
}
21+
22+
asyncLocalStorage.run(() => {
23+
const store = asyncLocalStorage.getStore();
24+
store.set('key', 'value');
25+
testAwait(); // should not reject
26+
});
27+
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use strict';
2+
require('../common');
3+
const assert = require('assert');
4+
const { AsyncLocalStorage } = require('async_hooks');
5+
6+
const asyncLocalStorage = new AsyncLocalStorage();
7+
8+
asyncLocalStorage.runSyncAndReturn(() => {
9+
asyncLocalStorage.getStore().set('foo', 'bar');
10+
process.nextTick(() => {
11+
assert.strictEqual(asyncLocalStorage.getStore().get('foo'), 'bar');
12+
asyncLocalStorage.disable();
13+
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
14+
process.nextTick(() => {
15+
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
16+
asyncLocalStorage.runSyncAndReturn(() => {
17+
assert.notStrictEqual(asyncLocalStorage.getStore(), undefined);
18+
});
19+
});
20+
});
21+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
'use strict';
2+
require('../common');
3+
const assert = require('assert');
4+
const { AsyncLocalStorage } = require('async_hooks');
5+
6+
// case 1 fully async APIS (safe)
7+
const asyncLocalStorage = new AsyncLocalStorage();
8+
9+
let i = 0;
10+
process.setUncaughtExceptionCaptureCallback((err) => {
11+
++i;
12+
assert.strictEqual(err.message, 'err' + i);
13+
assert.strictEqual(asyncLocalStorage.getStore().get('hello'), 'node');
14+
});
15+
16+
asyncLocalStorage.run(() => {
17+
const store = asyncLocalStorage.getStore();
18+
store.set('hello', 'node');
19+
setTimeout(() => {
20+
process.nextTick(() => {
21+
assert.strictEqual(i, 2);
22+
});
23+
throw new Error('err2');
24+
}, 0);
25+
throw new Error('err1');
26+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
'use strict';
2+
require('../common');
3+
const assert = require('assert');
4+
const { AsyncLocalStorage } = require('async_hooks');
5+
6+
// case 2 using *AndReturn calls (dual behaviors)
7+
const asyncLocalStorage = new AsyncLocalStorage();
8+
9+
let i = 0;
10+
process.setUncaughtExceptionCaptureCallback((err) => {
11+
++i;
12+
assert.strictEqual(err.message, 'err2');
13+
assert.strictEqual(asyncLocalStorage.getStore().get('hello'), 'node');
14+
});
15+
16+
try {
17+
asyncLocalStorage.runSyncAndReturn(() => {
18+
const store = asyncLocalStorage.getStore();
19+
store.set('hello', 'node');
20+
setTimeout(() => {
21+
process.nextTick(() => {
22+
assert.strictEqual(i, 1);
23+
});
24+
throw new Error('err2');
25+
}, 0);
26+
throw new Error('err1');
27+
});
28+
} catch (e) {
29+
assert.strictEqual(e.message, 'err1');
30+
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use strict';
2+
require('../common');
3+
const assert = require('assert');
4+
const { AsyncLocalStorage } = require('async_hooks');
5+
const http = require('http');
6+
7+
const asyncLocalStorage = new AsyncLocalStorage();
8+
const server = http.createServer((req, res) => {
9+
res.end('ok');
10+
});
11+
12+
server.listen(0, () => {
13+
asyncLocalStorage.run(() => {
14+
const store = asyncLocalStorage.getStore();
15+
store.set('hello', 'world');
16+
http.get({ host: 'localhost', port: server.address().port }, () => {
17+
assert.strictEqual(asyncLocalStorage.getStore().get('hello'), 'world');
18+
server.close();
19+
});
20+
});
21+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
'use strict';
2+
require('../common');
3+
const assert = require('assert');
4+
const { AsyncLocalStorage } = require('async_hooks');
5+
6+
const asyncLocalStorage = new AsyncLocalStorage();
7+
8+
setTimeout(() => {
9+
asyncLocalStorage.run(() => {
10+
const asyncLocalStorage2 = new AsyncLocalStorage();
11+
asyncLocalStorage2.run(() => {
12+
const store = asyncLocalStorage.getStore();
13+
const store2 = asyncLocalStorage2.getStore();
14+
store.set('hello', 'world');
15+
store2.set('hello', 'foo');
16+
setTimeout(() => {
17+
assert.strictEqual(asyncLocalStorage.getStore().get('hello'), 'world');
18+
assert.strictEqual(asyncLocalStorage2.getStore().get('hello'), 'foo');
19+
}, 200);
20+
});
21+
});
22+
}, 100);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
'use strict';
2+
require('../common');
3+
const assert = require('assert');
4+
const { AsyncLocalStorage } = require('async_hooks');
5+
6+
const asyncLocalStorage = new AsyncLocalStorage();
7+
const asyncLocalStorage2 = new AsyncLocalStorage();
8+
9+
setTimeout(() => {
10+
asyncLocalStorage.run(() => {
11+
asyncLocalStorage2.run(() => {
12+
const store = asyncLocalStorage.getStore();
13+
const store2 = asyncLocalStorage2.getStore();
14+
store.set('hello', 'world');
15+
store2.set('hello', 'foo');
16+
setTimeout(() => {
17+
assert.strictEqual(asyncLocalStorage.getStore().get('hello'), 'world');
18+
assert.strictEqual(asyncLocalStorage2.getStore().get('hello'), 'foo');
19+
asyncLocalStorage.exit(() => {
20+
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
21+
assert.strictEqual(asyncLocalStorage2.getStore().get('hello'), 'foo');
22+
});
23+
assert.strictEqual(asyncLocalStorage.getStore().get('hello'), 'world');
24+
assert.strictEqual(asyncLocalStorage2.getStore().get('hello'), 'foo');
25+
}, 200);
26+
});
27+
});
28+
}, 100);
29+
30+
setTimeout(() => {
31+
asyncLocalStorage.run(() => {
32+
const store = asyncLocalStorage.getStore();
33+
store.set('hello', 'earth');
34+
setTimeout(() => {
35+
assert.strictEqual(asyncLocalStorage.getStore().get('hello'), 'earth');
36+
}, 100);
37+
});
38+
}, 100);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
'use strict';
2+
require('../common');
3+
const assert = require('assert');
4+
const { AsyncLocalStorage } = require('async_hooks');
5+
6+
async function main() {
7+
const asyncLocalStorage = new AsyncLocalStorage();
8+
const err = new Error();
9+
const next = () => Promise.resolve()
10+
.then(() => {
11+
assert.strictEqual(asyncLocalStorage.getStore().get('a'), 1);
12+
throw err;
13+
});
14+
await new Promise((resolve, reject) => {
15+
asyncLocalStorage.run(() => {
16+
const store = asyncLocalStorage.getStore();
17+
store.set('a', 1);
18+
next().then(resolve, reject);
19+
});
20+
})
21+
.catch((e) => {
22+
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
23+
assert.strictEqual(e, err);
24+
});
25+
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
26+
}
27+
28+
main();

0 commit comments

Comments
 (0)
Please sign in to comment.