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: https://github.com/nodejs/node/pull/57766 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
This commit is contained in:
parent
4c5341ab73
commit
a369818b1e
@ -116,13 +116,16 @@ Each instance of `AsyncLocalStorage` maintains an independent storage context.
|
||||
Multiple instances can safely exist simultaneously without risk of interfering
|
||||
with each other's data.
|
||||
|
||||
### `new AsyncLocalStorage()`
|
||||
### `new AsyncLocalStorage([options])`
|
||||
|
||||
<!-- YAML
|
||||
added:
|
||||
- v13.10.0
|
||||
- v12.17.0
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/57766
|
||||
description: Add `defaultValue` and `name` options.
|
||||
- version:
|
||||
- v19.7.0
|
||||
- v18.16.0
|
||||
@ -135,6 +138,10 @@ changes:
|
||||
description: Add option onPropagate.
|
||||
-->
|
||||
|
||||
* `options` {Object}
|
||||
* `defaultValue` {any} The default value to be used when no store is provided.
|
||||
* `name` {string} A name for the `AsyncLocalStorage` value.
|
||||
|
||||
Creates a new instance of `AsyncLocalStorage`. Store is only provided within a
|
||||
`run()` call or after an `enterWith()` call.
|
||||
|
||||
@ -286,6 +293,16 @@ emitter.emit('my-event');
|
||||
asyncLocalStorage.getStore(); // Returns the same object
|
||||
```
|
||||
|
||||
### `asyncLocalStorage.name`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* {string}
|
||||
|
||||
The name of the `AsyncLocalStorage` instance if provided.
|
||||
|
||||
### `asyncLocalStorage.run(store, callback[, ...args])`
|
||||
|
||||
<!-- YAML
|
||||
|
@ -4,10 +4,37 @@ const {
|
||||
ReflectApply,
|
||||
} = primordials;
|
||||
|
||||
const {
|
||||
validateObject,
|
||||
} = require('internal/validators');
|
||||
|
||||
const AsyncContextFrame = require('internal/async_context_frame');
|
||||
const { AsyncResource } = require('async_hooks');
|
||||
|
||||
class AsyncLocalStorage {
|
||||
#defaultValue = undefined;
|
||||
#name = undefined;
|
||||
|
||||
/**
|
||||
* @typedef {object} AsyncLocalStorageOptions
|
||||
* @property {any} [defaultValue] - The default value to use when no value is set.
|
||||
* @property {string} [name] - The name of the storage.
|
||||
*/
|
||||
/**
|
||||
* @param {AsyncLocalStorageOptions} [options]
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
validateObject(options, 'options');
|
||||
this.#defaultValue = options.defaultValue;
|
||||
|
||||
if (options.name !== undefined) {
|
||||
this.#name = `${options.name}`;
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {string} */
|
||||
get name() { return this.#name || ''; }
|
||||
|
||||
static bind(fn) {
|
||||
return AsyncResource.bind(fn);
|
||||
}
|
||||
@ -40,7 +67,11 @@ class AsyncLocalStorage {
|
||||
}
|
||||
|
||||
getStore() {
|
||||
return AsyncContextFrame.current()?.get(this);
|
||||
const frame = AsyncContextFrame.current();
|
||||
if (!frame?.has(this)) {
|
||||
return this.#defaultValue;
|
||||
}
|
||||
return frame?.get(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,10 @@ const {
|
||||
Symbol,
|
||||
} = primordials;
|
||||
|
||||
const {
|
||||
validateObject,
|
||||
} = require('internal/validators');
|
||||
|
||||
const {
|
||||
AsyncResource,
|
||||
createHook,
|
||||
@ -27,11 +31,31 @@ const storageHook = createHook({
|
||||
});
|
||||
|
||||
class AsyncLocalStorage {
|
||||
constructor() {
|
||||
#defaultValue = undefined;
|
||||
#name = undefined;
|
||||
|
||||
/**
|
||||
* @typedef {object} AsyncLocalStorageOptions
|
||||
* @property {any} [defaultValue] - The default value to use when no value is set.
|
||||
* @property {string} [name] - The name of the storage.
|
||||
*/
|
||||
/**
|
||||
* @param {AsyncLocalStorageOptions} [options]
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
this.kResourceStore = Symbol('kResourceStore');
|
||||
this.enabled = false;
|
||||
validateObject(options, 'options');
|
||||
this.#defaultValue = options.defaultValue;
|
||||
|
||||
if (options.name !== undefined) {
|
||||
this.#name = `${options.name}`;
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {string} */
|
||||
get name() { return this.#name || ''; }
|
||||
|
||||
static bind(fn) {
|
||||
return AsyncResource.bind(fn);
|
||||
}
|
||||
@ -109,8 +133,12 @@ class AsyncLocalStorage {
|
||||
getStore() {
|
||||
if (this.enabled) {
|
||||
const resource = executionAsyncResource();
|
||||
if (!(this.kResourceStore in resource)) {
|
||||
return this.#defaultValue;
|
||||
}
|
||||
return resource[this.kResourceStore];
|
||||
}
|
||||
return this.#defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
40
test/parallel/test-als-defaultvalue-original.js
Normal file
40
test/parallel/test-als-defaultvalue-original.js
Normal file
@ -0,0 +1,40 @@
|
||||
// Flags: --no-async-context-frame
|
||||
'use strict';
|
||||
|
||||
require('../common');
|
||||
|
||||
const {
|
||||
AsyncLocalStorage,
|
||||
} = require('async_hooks');
|
||||
|
||||
const {
|
||||
strictEqual,
|
||||
throws,
|
||||
} = require('assert');
|
||||
|
||||
// ============================================================================
|
||||
// The defaultValue option
|
||||
const als1 = new AsyncLocalStorage();
|
||||
strictEqual(als1.getStore(), undefined, 'value should be undefined');
|
||||
|
||||
const als2 = new AsyncLocalStorage({ defaultValue: 'default' });
|
||||
strictEqual(als2.getStore(), 'default', 'value should be "default"');
|
||||
|
||||
const als3 = new AsyncLocalStorage({ defaultValue: 42 });
|
||||
strictEqual(als3.getStore(), 42, 'value should be 42');
|
||||
|
||||
const als4 = new AsyncLocalStorage({ defaultValue: null });
|
||||
strictEqual(als4.getStore(), null, 'value should be null');
|
||||
|
||||
throws(() => new AsyncLocalStorage(null), {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// The name option
|
||||
|
||||
const als5 = new AsyncLocalStorage({ name: 'test' });
|
||||
strictEqual(als5.name, 'test');
|
||||
|
||||
const als6 = new AsyncLocalStorage();
|
||||
strictEqual(als6.name, '');
|
40
test/parallel/test-als-defaultvalue.js
Normal file
40
test/parallel/test-als-defaultvalue.js
Normal file
@ -0,0 +1,40 @@
|
||||
// Flags: --async-context-frame
|
||||
'use strict';
|
||||
|
||||
require('../common');
|
||||
|
||||
const {
|
||||
AsyncLocalStorage,
|
||||
} = require('async_hooks');
|
||||
|
||||
const {
|
||||
strictEqual,
|
||||
throws,
|
||||
} = require('assert');
|
||||
|
||||
// ============================================================================
|
||||
// The defaultValue option
|
||||
const als1 = new AsyncLocalStorage();
|
||||
strictEqual(als1.getStore(), undefined, 'value should be undefined');
|
||||
|
||||
const als2 = new AsyncLocalStorage({ defaultValue: 'default' });
|
||||
strictEqual(als2.getStore(), 'default', 'value should be "default"');
|
||||
|
||||
const als3 = new AsyncLocalStorage({ defaultValue: 42 });
|
||||
strictEqual(als3.getStore(), 42, 'value should be 42');
|
||||
|
||||
const als4 = new AsyncLocalStorage({ defaultValue: null });
|
||||
strictEqual(als4.getStore(), null, 'value should be null');
|
||||
|
||||
throws(() => new AsyncLocalStorage(null), {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// The name option
|
||||
|
||||
const als5 = new AsyncLocalStorage({ name: 'test' });
|
||||
strictEqual(als5.name, 'test');
|
||||
|
||||
const als6 = new AsyncLocalStorage();
|
||||
strictEqual(als6.name, '');
|
Loading…
x
Reference in New Issue
Block a user