Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion doc/api/async_context.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.

Expand Down Expand Up @@ -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
Expand Down
33 changes: 32 additions & 1 deletion lib/internal/async_local_storage/async_context_frame.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}
}

Expand Down
30 changes: 29 additions & 1 deletion lib/internal/async_local_storage/async_hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ const {
Symbol,
} = primordials;

const {
validateObject,
} = require('internal/validators');

const {
AsyncResource,
createHook,
Expand All @@ -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);
}
Expand Down Expand Up @@ -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;
}
}

Expand Down
40 changes: 40 additions & 0 deletions test/parallel/test-als-defaultvalue-original.js
Original file line number Diff line number Diff line change
@@ -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 changes: 40 additions & 0 deletions test/parallel/test-als-defaultvalue.js
Original file line number Diff line number Diff line change
@@ -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