lib: add defaultValue and name options to AsyncLocalStorage · nodejs/node@c3e4434 · GitHub
Skip to content

Commit c3e4434

Browse files
jasnellRafaelGSS
authored andcommitted
lib: add defaultValue and name options to AsyncLocalStorage
The upcoming `AsyncContext` specification adds a default value and name to async storage variables. This adds the same to `AsyncLocalStorage` to promote closer alignment with the pending spec. ```js const als = new AsyncLocalStorage({ name: 'foo', defaultValue: 123, }); console.log(als.name); // 'foo' console.log(als.getStore()); // 123 ``` Refs: https://github.com/tc39/proposal-async-context/blob/master/spec.html PR-URL: #57766 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
1 parent f99f815 commit c3e4434

5 files changed

Lines changed: 159 additions & 3 deletions

File tree

doc/api/async_context.md

Lines changed: 18 additions & 1 deletion

lib/internal/async_local_storage/async_context_frame.js

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,37 @@ const {
44
ReflectApply,
55
} = primordials;
66

7+
const {
8+
validateObject,
9+
} = require('internal/validators');
10+
711
const AsyncContextFrame = require('internal/async_context_frame');
812
const { AsyncResource } = require('async_hooks');
913

1014
class AsyncLocalStorage {
15+
#defaultValue = undefined;
16+
#name = undefined;
17+
18+
/**
19+
* @typedef {object} AsyncLocalStorageOptions
20+
* @property {any} [defaultValue] - The default value to use when no value is set.
21+
* @property {string} [name] - The name of the storage.
22+
*/
23+
/**
24+
* @param {AsyncLocalStorageOptions} [options]
25+
*/
26+
constructor(options = {}) {
27+
validateObject(options, 'options');
28+
this.#defaultValue = options.defaultValue;
29+
30+
if (options.name !== undefined) {
31+
this.#name = `${options.name}`;
32+
}
33+
}
34+
35+
/** @type {string} */
36+
get name() { return this.#name || ''; }
37+
1138
static bind(fn) {
1239
return AsyncResource.bind(fn);
1340
}
@@ -40,7 +67,11 @@ class AsyncLocalStorage {
4067
}
4168

4269
getStore() {
43-
return AsyncContextFrame.current()?.get(this);
70+
const frame = AsyncContextFrame.current();
71+
if (!frame?.has(this)) {
72+
return this.#defaultValue;
73+
}
74+
return frame?.get(this);
4475
}
4576
}
4677

lib/internal/async_local_storage/async_hooks.js

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ const {
99
Symbol,
1010
} = primordials;
1111

12+
const {
13+
validateObject,
14+
} = require('internal/validators');
15+
1216
const {
1317
AsyncResource,
1418
createHook,
@@ -27,11 +31,31 @@ const storageHook = createHook({
2731
});
2832

2933
class AsyncLocalStorage {
30-
constructor() {
34+
#defaultValue = undefined;
35+
#name = undefined;
36+
37+
/**
38+
* @typedef {object} AsyncLocalStorageOptions
39+
* @property {any} [defaultValue] - The default value to use when no value is set.
40+
* @property {string} [name] - The name of the storage.
41+
*/
42+
/**
43+
* @param {AsyncLocalStorageOptions} [options]
44+
*/
45+
constructor(options = {}) {
3146
this.kResourceStore = Symbol('kResourceStore');
3247
this.enabled = false;
48+
validateObject(options, 'options');
49+
this.#defaultValue = options.defaultValue;
50+
51+
if (options.name !== undefined) {
52+
this.#name = `${options.name}`;
53+
}
3354
}
3455

56+
/** @type {string} */
57+
get name() { return this.#name || ''; }
58+
3559
static bind(fn) {
3660
return AsyncResource.bind(fn);
3761
}
@@ -109,8 +133,12 @@ class AsyncLocalStorage {
109133
getStore() {
110134
if (this.enabled) {
111135
const resource = executionAsyncResource();
136+
if (!(this.kResourceStore in resource)) {
137+
return this.#defaultValue;
138+
}
112139
return resource[this.kResourceStore];
113140
}
141+
return this.#defaultValue;
114142
}
115143
}
116144

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Flags: --no-async-context-frame
2+
'use strict';
3+
4+
require('../common');
5+
6+
const {
7+
AsyncLocalStorage,
8+
} = require('async_hooks');
9+
10+
const {
11+
strictEqual,
12+
throws,
13+
} = require('assert');
14+
15+
// ============================================================================
16+
// The defaultValue option
17+
const als1 = new AsyncLocalStorage();
18+
strictEqual(als1.getStore(), undefined, 'value should be undefined');
19+
20+
const als2 = new AsyncLocalStorage({ defaultValue: 'default' });
21+
strictEqual(als2.getStore(), 'default', 'value should be "default"');
22+
23+
const als3 = new AsyncLocalStorage({ defaultValue: 42 });
24+
strictEqual(als3.getStore(), 42, 'value should be 42');
25+
26+
const als4 = new AsyncLocalStorage({ defaultValue: null });
27+
strictEqual(als4.getStore(), null, 'value should be null');
28+
29+
throws(() => new AsyncLocalStorage(null), {
30+
code: 'ERR_INVALID_ARG_TYPE',
31+
});
32+
33+
// ============================================================================
34+
// The name option
35+
36+
const als5 = new AsyncLocalStorage({ name: 'test' });
37+
strictEqual(als5.name, 'test');
38+
39+
const als6 = new AsyncLocalStorage();
40+
strictEqual(als6.name, '');
Lines changed: 40 additions & 0 deletions

0 commit comments

Comments
 (0)