From 3d48742cc0795237a0cd228363e22e10ba31b896 Mon Sep 17 00:00:00 2001 From: Chengzhong Wu Date: Fri, 5 May 2023 15:38:56 +0800 Subject: [PATCH 01/10] Add AsyncSnapshot and AsyncLocal --- README.md | 105 ++++++++------- SCOPING.md | 72 +++++----- spec.html | 380 +++++++++++++++++++++-------------------------------- 3 files changed, 241 insertions(+), 316 deletions(-) diff --git a/README.md b/README.md index a1a54c6..5d5c751 100644 --- a/README.md +++ b/README.md @@ -150,85 +150,85 @@ Non-goals: # Proposed Solution -`AsyncContext` are designed as a value store for context propagation across +`AsyncLocal` are designed as a value store for context propagation across logically-connected sync/async code execution. ```typescript -class AsyncContext { - static wrap(callback: (...args: any[]) => R): (...args: any[]) => R; - - constructor(options: AsyncContextOptions); +class AsyncLocal { + constructor(options: AsyncLocalOptions); get name(): string; - run(value: T, callback: () => R): R; + run(value: T, fn: () => R): R; get(): T | undefined; } -interface AsyncContextOptions { +interface AsyncLocalOptions { name?: string; defaultValue?: T; } + +class AsyncSnapshot { + constructor(); + + run(fn: (...args: any[]) => R, ...args: any[]): R; +} ``` -`AsyncContext.prototype.run()` and `AsyncContext.prototype.get()` sets and gets -the current value of an async execution flow. `AsyncContext.wrap()` allows you -to opaquely capture the current value of all `AsyncContext`s and execute the -callback at a later time with as if those values were still the current values -(a snapshot and restore). Note that even with `AsyncContext.wrap()`, you can -only access the value associated with an `AsyncContext` instance if you have +`AsyncLocal.prototype.run()` and `AsyncLocal.prototype.get()` sets and gets +the current value of an async execution flow. `AsyncSnapshot` allows you +to opaquely capture the current value of all `AsyncLocal`s and execute a +function at a later time with as if those values were still the current values +(a snapshot and restore). Note that even with `AsyncSnapshot`, you can +only access the value associated with an `AsyncLocal` instance if you have access to that instance. ```typescript -const context = new AsyncContext(); +const asyncLocal = new AsyncLocal(); // Sets the current value to 'top', and executes the `main` function. -context.run("top", main); +asyncLocal.run("top", main); function main() { - // Context is maintained through other platform queueing. + // AsyncLocal is maintained through other platform queueing. setTimeout(() => { - console.log(context.get()); // => 'top' + console.log(asyncLocal.get()); // => 'top' - context.run("A", () => { - console.log(context.get()); // => 'A' + asyncLocal.run("A", () => { + console.log(asyncLocal.get()); // => 'A' setTimeout(() => { - console.log(context.get()); // => 'A' + console.log(asyncLocal.get()); // => 'A' }, randomTimeout()); }); }, randomTimeout()); - // Context runs can be nested. - context.run("B", () => { - console.log(context.get()); // => 'B' + // AsyncLocal runs can be nested. + asyncLocal.run("B", () => { + console.log(asyncLocal.get()); // => 'B' setTimeout(() => { - console.log(context.get()); // => 'B' + console.log(asyncLocal.get()); // => 'B' }, randomTimeout()); }); - // Context was restored after the previous run. - console.log(context.get()); // => 'top' + // AsyncLocal was restored after the previous run. + console.log(asyncLocal.get()); // => 'top' - // Captures the state of all AsyncContext's at this moment. - const snapshotDuringTop = AsyncContext.wrap((cb) => { - console.log(context.get()); // => 'top' - cb(); - }); + // Captures the state of all AsyncLocal's at this moment. + const snapshotDuringTop = new AsyncSnapshot(); - // Context runs can be nested. - context.run("C", () => { - console.log(context.get()); // => 'C' + asyncLocal.run("C", () => { + console.log(asyncLocal.get()); // => 'C' - // The snapshotDuringTop will restore all AsyncContext to their snapshot - // state and invoke the wrapped function. We pass a callback which it will + // The snapshotDuringTop will restore all AsyncLocal to their snapshot + // state and invoke the wrapped function. We pass a function which it will // invoke. - snapshotDuringTop(() => { + snapshotDuringTop.run(() => { // Despite being lexically nested inside 'C', the snapshot restored us to // to the 'top' state. - console.log(context.get()); // => 'top' + console.log(asyncLocal.get()); // => 'top' }); }); } @@ -238,7 +238,7 @@ function randomTimeout() { } ``` -`AsyncContext.wrap` is useful for implementing APIs that logically "schedule" a +`AsyncSnapshot` is useful for implementing APIs that logically "schedule" a callback, so the callback will be called with the context that it logically belongs to, regardless of the context under which it actually runs: @@ -247,7 +247,10 @@ let queue = []; export function enqueueCallback(cb: () => void) { // Each callback is stored with the context at which it was enqueued. - queue.push(AsyncContext.wrap(cb)); + const snapshot = new AsyncSnapshot(); + queue.push(() => { + snapshot.run(cb); + }); } runWhenIdle(() => { @@ -261,11 +264,11 @@ runWhenIdle(() => { ``` > Note: There are controversial thought on the dynamic scoping and -> `AsyncContext`, checkout [SCOPING.md][] for more details. +> `AsyncLocal`, checkout [SCOPING.md][] for more details. ## Use cases -Use cases for `AsyncContext` include: +Use cases for async context include: - Annotating logs with information related to an asynchronous callstack. @@ -300,7 +303,7 @@ A detailed example usecase can be found [here](./USE-CASES.md) ## Determine the initiator of a task Application monitoring tools like OpenTelemetry save their tracing spans in the -`AsyncContext` and retrieve the span when they need to determine what started +`AsyncLocal` and retrieve the span when they need to determine what started this chain of interaction. These libraries can not intrude the developer APIs for seamless monitoring. The @@ -309,7 +312,7 @@ tracing span doesn't need to be manually passing around by usercodes. ```typescript // tracer.js -const context = new AsyncContext(); +const asyncLocal = new AsyncLocal(); export function run(cb) { // (a) const span = { @@ -317,12 +320,12 @@ export function run(cb) { traceId: randomUUID(), spanId: randomUUID(), }; - context.run(span, cb); + asyncLocal.run(span, cb); } export function end() { // (b) - const span = context.get(); + const span = asyncLocal.get(); span?.endTime = Date.now(); } ``` @@ -358,20 +361,20 @@ concurrent multi-tracking. ## Transitive task attribution -User tasks can be scheduled with attributions. With `AsyncContext`, task +User tasks can be scheduled with attributions. With `AsyncLocal`, task attributions are propagated in the async task flow and sub-tasks can be scheduled with the same priority. ```typescript const scheduler = { - context: new AsyncContext(), + asyncLocal: new AsyncLocal(), postTask(task, options) { // In practice, the task execution may be deferred. - // Here we simply run the task immediately with the context. - return this.context.run({ priority: options.priority }, task); + // Here we simply run the task immediately. + return this.asyncLocal.run({ priority: options.priority }, task); }, currentTask() { - return this.context.get() ?? { priority: "default" }; + return this.asyncLocal.get() ?? { priority: "default" }; }, }; diff --git a/SCOPING.md b/SCOPING.md index 5a1d712..33a742c 100644 --- a/SCOPING.md +++ b/SCOPING.md @@ -1,8 +1,8 @@ -# Scoping of AsyncContext +# Scoping of AsyncLocal -The major concerns of `AsyncContext` advancing to Stage 1 of TC39 proposal +The major concerns of `AsyncLocal` advancing to Stage 1 of TC39 proposal process is that there are potential dynamic scoping of the semantics of -`AsyncContext`. This document is about defining the scoping of `AsyncContext`. +`AsyncLocal`. This document is about defining the scoping of `AsyncLocal`. ### Dynamic Scoping @@ -22,61 +22,61 @@ $ echo $x # does this print 1, or 2? 1 ``` -However, the naming scope of an async context is identical to a regular variable +However, the naming scope of an `AsyncLocal` is identical to a regular variable in JavaScript. Since JavaScript variables are lexically scoped, the naming of -async context instances are lexically scoped too. It is not possible to access a -value inside an async context without explicit access to the async context +`AsyncLocal` instances are lexically scoped too. It is not possible to access a +value inside an `AsyncLocal` without explicit access to the `AsyncLocal` instance itself. ```typescript -const context = new AsyncContext(); +const asyncLocal = new AsyncLocal(); -context.run(1, f); -console.log(context.get()); // => undefined +asyncLocal.run(1, f); +console.log(asyncLocal.get()); // => undefined function g() { - console.log(context.get()); // => 1 + console.log(asyncLocal.get()); // => 1 } function f() { - // Intentionally named the same "context" - const context = new AsyncContext(); - context.run(2, g); + // Intentionally named the same "asyncLocal" + const asyncLocal = new AsyncLocal(); + asyncLocal.run(2, g); } ``` -Hence, knowing the name of an async context variable does not give you the -ability to change that context. You must have direct access to it in order to -affect it. +Hence, knowing the name of an `AsyncLocal` variable does not give you the +ability to change the value of that variable. You must have direct access to it +in order to affect it. ```typescript -const context = new AsyncContext(); +const asyncLocal = new AsyncLocal(); -context.run(1, f); +asyncLocal.run(1, f); -console.log(context.get()); // => undefined; +console.log(asyncLocal.get()); // => undefined; function f() { - const context = new AsyncContext(); - context.run(2, g); + const asyncLocal = new AsyncLocal(); + asyncLocal.run(2, g); function g() { - console.log(context.get(); // => 2; + console.log(asyncLocal.get()); // => 2; } } ``` ### Dynamic Scoping: dependency on caller -One argument on the dynamic scoping is that the values in `AsyncContext` can be +One argument on the dynamic scoping is that the values in `AsyncLocal` can be changed depending on which the caller is. -However, the definition of whether the value of an async context can be changed +However, the definition of whether the value of an `AsyncLocal` can be changed has the same meaning with a regular JavaScript variable: anyone with direct access to a variable has the ability to change the variable. ```typescript -class SyncContext { +class SyncLocal { #current; get() { @@ -94,30 +94,30 @@ class SyncContext { } } -const context = new SyncContext(); +const syncLocal = new SyncLocal(); -context.run(1, f); +syncLocal.run(1, f); -console.log(context.get()); // => undefined; +console.log(syncLocal.get()); // => undefined; function g() { - console.log(context.get()); // => 1 + console.log(syncLocal.get()); // => 1 } function f() { - // Intentionally named the same "context" - const context = new AsyncContext(); - context.run(2, g); + // Intentionally named the same "syncLocal" + const syncLocal = new AsyncLocal(); + syncLocal.run(2, g); } ``` -If this userland `SyncContext` is acceptable, than adding an `AsyncContext` +If this userland `SyncLocal` is acceptable, than adding an `AsyncLocal` that can operate across sync/async execution should be no different. ### Summary -There are no differences regarding naming scope of async contexts compared to -regular JavaScript variables. Only code with direct access to `AsyncContex` +There are no differences regarding naming scope of `AsyncLocal` compared to +regular JavaScript variables. Only code with direct access to `AsyncLocal` instances can modify the value, and only for code execution nested inside a new -`context.run()`. Further, the capability to modify a local variable which you +`asyncLocal.run()`. Further, the capability to modify a local variable which you have direct access to is already possible in sync code execution. diff --git a/spec.html b/spec.html index fa20434..9a15121 100644 --- a/spec.html +++ b/spec.html @@ -65,10 +65,10 @@

Agents

[[AsyncContextMapping]] - a List of Records with fields [[AsyncContextKey]] (a Symbol) and [[AsyncContextValue]] (an ECMAScript language value) + a List of Records with fields [[AsyncLocalKey]] (a Symbol) and [[AsyncLocalValue]] (an ECMAScript language value) - A map from the AsyncContext's key symbol to the saved ECMAScript language value. Every Record in the List contains a unique [[AsyncContextKey]]. + A map from the AsyncContext's key symbol to the saved ECMAScript language value. Every Record in the List contains a unique [[AsyncLocalKey]]. @@ -110,10 +110,10 @@

JobCallback Records

[[AsyncContextSnapshot]] - a List of Records with fields [[AsyncContextKey]] (a Symbol) and [[AsyncContextValue]] (an ECMAScript language value) + a List of Records with fields [[AsyncLocalKey]] (a Symbol) and [[AsyncLocalValue]] (an ECMAScript language value) - A map from the AsyncContext's key symbol to the saved ECMAScript language value. Every Record in the List contains a unique [[AsyncContextKey]]. + A map from the AsyncContext's key symbol to the saved ECMAScript language value. Every Record in the List contains a unique [[AsyncLocalKey]]. @@ -189,147 +189,18 @@

- - -

Ordinary and Exotic Objects Behaviours

- -

Built-in Exotic Object Internal Methods and Slots

- - -

AsyncContext Wrapped Function Exotic Objects

- - -

This is based on

-
- -

An AsyncContext wrapped function exotic object is an exotic object that wraps another function object. An AsyncContext wrapped function exotic object is callable (it has a [[Call]] internal method and may have a [[Construct]] internal method). Calling an AsyncContext wrapped function exotic object generally results in a call of its wrapped function.

- -

An object is a AsyncContext wrapped function exotic object if its [[Call]] and (if applicable) [[Construct]] internal methods use the following implementations, and its other essential internal methods use the definitions found in Ordinary Object Internal Methods and Internal Slots. These methods are installed in AsyncContextWrappedFunctionCreate.

- -

Async context wrapped function exotic objects do not have the internal slots of ECMAScript function objects listed in Internal Slots of ECMAScript Function Objects. Instead they have the internal slots listed in , in addition to [[Prototype]] and [[Extensible]].

- - - - - - - - - - - - - - - - - -
- Internal Slot - - Type - - Description -
- [[WrappedTargetFunction]] - - a callable Object - - The wrapped function object. -
- [[AsyncContextSnapshot]] - - a List of Records with fields [[AsyncContextKey]] (a Symbol) and [[AsyncContextValue]] (an ECMAScript language value) - - A map from the AsyncContext's key symbol to the saved ECMAScript language value. Every Record in the List contains a unique [[AsyncContextKey]]. -
-
- - -

- [[Call]] ( - _thisArgument_: an ECMAScript language value, - _argumentsList_: a List of ECMAScript language values, - ): either a normal completion containing an ECMAScript language value or a throw completion -

-
-
for
-
an AsyncContext wrapped function exotic object _F_
-
- - 1. Let _target_ be F.[[WrappedTargetFunction]]. - 1. Let _snapshot_ be F.[[AsyncContextSnapshot]]. - 1. Let _previousContextMapping_ be AsyncContextSwap(_snapshot_). - 1. Let _result_ be Completion(Call(_target_, _thisArgument_, _argumentsList_)). - 1. AsyncContextSwap(_previousContextMapping_). - 1. Return _result_. - -
- - -

- [[Construct]] ( - _argumentsList_: a List of ECMAScript language values, - _newTarget_: a constructor, - ): either a normal completion containing an Object or a throw completion -

-
-
for
-
an AsyncContext wrapped function exotic object _F_
-
- - 1. Let _target_ be _F_.[[WrappedTargetFunction]]. - 1. Assert: IsConstructor(_target_) is *true*. - 1. Let _snapshot_ be F.[[AsyncContextSnapshot]]. - 1. Let _previousContextMapping_ be AsyncContextSwap(_snapshot_). - 1. If SameValue(_F_, _newTarget_) is *true*, set _newTarget_ to _target_. - 1. Let _result_ be Completion(Construct(_target_, _argumentsList_, _newTarget_)). - 1. AsyncContextSwap(_previousContextMapping_). - 1. Return _result_. - -
- - -

- AsyncContextWrappedFunctionCreate ( - _targetFunction_: a function object, - _snapshot_: a List of Records with fields [[AsyncContextKey]] (a Symbol) and [[AsyncContextValue]] (an ECMAScript language value), - ): either a normal completion containing a function object or a throw completion -

-
-
description
-
It is used to specify the creation of new AsyncContext wrapped function exotic objects.
-
- - 1. Let _proto_ be ? _targetFunction_.[[GetPrototypeOf]](). - 1. Let _internalSlotsList_ be the list-concatenation of « [[Prototype]], [[Extensible]] » and the internal slots listed in . - 1. Let _obj_ be MakeBasicObject(_internalSlotsList_). - 1. Set _obj_.[[Prototype]] to _proto_. - 1. Set _obj_.[[Call]] as described in . - 1. If IsConstructor(_targetFunction_) is *true*, then - 1. Set _obj_.[[Construct]] as described in . - 1. Set _obj_.[[WrappedTargetFunction]] to _targetFunction_. - 1. Set _obj_.[[AsyncContextSnapshot]] to _snapshot_. - 1. Return _obj_. - -
-
-
-
-
-

Control Abstraction Objects

- -

AsyncContext Objects

+ +

AsyncSnapshot Objects

- -

AsyncContext Abstract Operations

+ +

AsyncSnapshot Abstract Operations

AsyncContextSnapshot ( - ): a List of Records with fields [[AsyncContextKey]] (a Symbol) and [[AsyncContextValue]] (an ECMAScript language value) + ): a List of Records with fields [[AsyncLocalKey]] (a Symbol) and [[AsyncLocalValue]] (an ECMAScript language value)

description
@@ -344,8 +215,8 @@

AsyncContextSwap ( - _snapshot_: a List of Records with fields [[AsyncContextKey]] (a Symbol) and [[AsyncContextValue]] (an ECMAScript language value) - ): a List of Records with fields [[AsyncContextKey]] (a Symbol) and [[AsyncContextValue]] (an ECMAScript language value) + _snapshot_: a List of Records with fields [[AsyncLocalKey]] (a Symbol) and [[AsyncLocalValue]] (an ECMAScript language value) + ): a List of Records with fields [[AsyncLocalKey]] (a Symbol) and [[AsyncLocalValue]] (an ECMAScript language value)

description
@@ -360,99 +231,150 @@

- -

The AsyncContext Constructor

-

The AsyncContext constructor:

+ +

The AsyncSnapshot Constructor

+

The AsyncSnapshot constructor:

    -
  • is %AsyncContext%.
  • -
  • is the initial value of the *"AsyncContext"* property of the global object.
  • -
  • creates and initializes a new AsyncContext when called as a constructor.
  • +
  • is %AsyncSnapshot%.
  • +
  • is the initial value of the *"AsyncSnapshot"* property of the global object.
  • +
  • creates and initializes a new AsyncSnapshot when called as a constructor.
  • is not intended to be called as a function and will throw an exception when called in that manner.
  • -
  • may be used as the value in an `extends` clause of a class definition. Subclass constructors that intend to inherit the specified AsyncContext behaviour must include a `super` call to the AsyncContext constructor to create and initialize the subclass instance with the internal state necessary to support the `AsyncContext` and `AsyncContext.prototype` built-in methods.
  • +
  • may be used as the value in an `extends` clause of a class definition. Subclass constructors that intend to inherit the specified AsyncSnapshot behaviour must include a `super` call to the AsyncSnapshot constructor to create and initialize the subclass instance with the internal state necessary to support the `AsyncSnapshot` and `AsyncSnapshot.prototype` built-in methods.
- -

AsyncContext ( _options_ )

+ +

AsyncSnapshot ( )

This function performs the following steps when called:

1. If NewTarget is *undefined*, throw a *TypeError* exception. - 1. If _options_ is an Object, then - 1. Let _name_ be ? Get(_options_, *"name"*). - 1. Let _nameStr_ be ? ToString(_name_). - 1. Let _defaultValue_ be ? Get(_options_, *"defaultValue"*). - 1. Else, - 1. Let _nameStr_ be the empty String. - 1. Let _defaultValue_ be *undefined*. - 1. Let _asyncContext_ be ? OrdinaryCreateFromConstructor(NewTarget, *"%AsyncContext.prototype%"*, « [[AsyncContextKey]] »). - 1. Set _asyncContext_.[[AsyncContextKey]] to a new Symbol whose [[Description]] is _nameStr_. - 1. Set _asyncContext_.[[AsyncContextDefaultValue]] to _defaultValue_. - 1. Return _asyncContext_. + 1. Let _snapshot_ be AsyncContextSnapshot(). + 1. Let _asyncSnapshot_ be ? OrdinaryCreateFromConstructor(NewTarget, *"%AsyncSnapshot.prototype%"*, « [[AsyncSnapshotMapping]] »). + 1. Set _asyncSnapshot_.[[AsyncSnapshotMapping]] to _snapshot_. + 1. Return _asyncSnapshot_.
- -

Properties of the AsyncContext Constructor

-

The AsyncContext constructor:

+ +

Properties of the AsyncSnapshot Prototype Object

+

The AsyncSnapshot prototype object:

    -
  • has a [[Prototype]] internal slot whose value is %Function.prototype%.
  • -
  • has the following properties:
  • +
  • is %AsyncSnapshot.prototype%.
  • +
  • has a [[Prototype]] internal slot whose value is %Object.prototype%.
  • +
  • is an ordinary object.
  • +
  • does not have any of the other internal slots of AsyncSnapshot instances.
- -

AsyncContext.wrap ( _target_ )

-

This function returns a new function which opaquely captures the current value of all AsyncContexts and restores the captured value when being invoked.

+ +

AsyncSnapshot.prototype.run ( _func_, ..._args_ )

+

This method performs the following steps when called:

+ + 1. Let _asyncSnapshot_ be the *this* value. + 1. Perform ? RequireInternalSlot(_asyncSnapshot_, [[AsyncSnapshotMapping]]). + 1. Let _snapshot_ be _asyncSnapshot_.[[AsyncSnapshotMapping]]. + 1. Let _contextMapping_ be AsyncContextSnapshot(). + 1. AsyncContextSwap(_snapshot_). + 1. Let _result_ be Completion(Call(_func_, *undefined*, _args_)). + 1. AsyncContextSwap(_contextMapping_). + 1. Return _result_. + +
+
+ + +

Properties of AsyncSnapshot Instances

+

AsyncSnapshot instances are ordinary objects that inherit properties from the AsyncSnapshot prototype object (the intrinsic, %AsyncSnapshot.prototype%). AsyncSnapshot instances are initially created with the internal slots described in .

+ + + + + + + + + + + + + +
+ Internal Slot + + Type + + Description +
+ [[AsyncSnapshotMapping]] + + a List of Records with fields [[AsyncLocalKey]] (a Symbol) and [[AsyncLocalValue]] (an ECMAScript language value) + + Represents the snapshotted surrounding agent's Agent Record's [[AsyncContextMapping]] of the AsyncSnapshot instance. +
+
+
+
+ + +

AsyncLocal Objects

+ + +

The AsyncLocal Constructor

+

The AsyncLocal constructor:

+
    +
  • is %AsyncLocal%.
  • +
  • is the initial value of the *"AsyncLocal"* property of the global object.
  • +
  • creates and initializes a new AsyncLocal when called as a constructor.
  • +
  • is not intended to be called as a function and will throw an exception when called in that manner.
  • +
  • may be used as the value in an `extends` clause of a class definition. Subclass constructors that intend to inherit the specified AsyncLocal behaviour must include a `super` call to the AsyncLocal constructor to create and initialize the subclass instance with the internal state necessary to support the `AsyncLocal` and `AsyncLocal.prototype` built-in methods.
  • +
+ + +

AsyncLocal ( _options_ )

+

This function performs the following steps when called:

- 1. Let _snapshot_ be AsyncContextSnapshot(). - 1. If IsCallable(_target_) is false, throw a TypeError exception. - 1. Let _F_ be ? AsyncContextWrappedFunctionCreate(_target_, _snapshot_). - 1. Let _L_ be 0. - 1. Let _targetHasLength_ be ? HasOwnProperty(_target_, *"length"*). - 1. If _targetHasLength_ is *true*, then - 1. Let _targetLen_ be ? Get(_target_, *"length"*). - 1. If _targetLen_ is a Number, then - 1. If _targetLen_ is *+∞*𝔽, set _L_ to +∞. - 1. Else if _targetLen_ is *-∞*𝔽, set _L_ to 0. - 1. Else, - 1. Let _targetLenAsInt_ be ! ToIntegerOrInfinity(_targetLen_). - 1. Assert: _targetLenAsInt_ is finite. - 1. Set _L_ to _targetLenAsInt_. - 1. Perform SetFunctionLength(_F_, _L_). - 1. Let _targetName_ be ? Get(_target_, *"name"*). - 1. If _targetName_ is not a String, set _targetName_ to the empty String. - 1. Perform SetFunctionName(_F_, _targetName_, *"wrapped"*). - 1. Return _F_. + 1. If NewTarget is *undefined*, throw a *TypeError* exception. + 1. If _options_ is an Object, then + 1. Let _name_ be ? Get(_options_, *"name"*). + 1. Let _nameStr_ be ? ToString(_name_). + 1. Let _defaultValue_ be ? Get(_options_, *"defaultValue"*). + 1. Else, + 1. Let _nameStr_ be the empty String. + 1. Let _defaultValue_ be *undefined*. + 1. Let _asyncLocal_ be ? OrdinaryCreateFromConstructor(NewTarget, *"%AsyncLocal.prototype%"*, « [[AsyncLocalKey]], [[AsyncLocalDefaultValue]] »). + 1. Set _asyncLocal_.[[AsyncLocalKey]] to a new Symbol whose [[Description]] is _nameStr_. + 1. Set _asyncLocal_.[[AsyncLocalDefaultValue]] to _defaultValue_. + 1. Return _asyncLocal_.
- -

Properties of the AsyncContext Prototype Object

-

The AsyncContext prototype object:

+ +

Properties of the AsyncLocal Prototype Object

+

The AsyncLocal prototype object:

    -
  • is %AsyncContext.prototype%.
  • +
  • is %AsyncLocal.prototype%.
  • has a [[Prototype]] internal slot whose value is %Object.prototype%.
  • is an ordinary object.
  • -
  • does not have any of the other internal slots of AsyncContext instances.
  • +
  • does not have any of the other internal slots of AsyncLocal instances.
- -

AsyncContext.prototype.run ( _value_, _func_, ..._args_ )

+ +

AsyncLocal.prototype.run ( _value_, _func_, ..._args_ )

This method performs the following steps when called:

- 1. Let _asyncContext_ be the *this* value. - 1. Perform ? RequireInternalSlot(_asyncContext_, [[AsyncContextKey]]). - 1. Let _asyncContextKey_ be _asyncContext_.[[AsyncContextKey]]. + 1. Let _asyncLocal_ be the *this* value. + 1. Perform ? RequireInternalSlot(_asyncLocal_, [[AsyncLocalKey]]). + 1. Let _AsyncLocalKey_ be _asyncLocal_.[[AsyncLocalKey]]. 1. Let _previousContextMapping_ be AsyncContextSnapshot(). 1. Let _asyncContextMapping_ be a new empty List. - 1. For each Record { [[AsyncContextKey]], [[AsyncContextValue]] } _p_ of _previousContextMapping_, do - 1. If SameValueZero(_p_.[[AsyncContextKey]], _asyncContextKey_) is *false*, then - 1. Let _q_ be the Record { [[AsyncContextKey]]: _p_.[[AsyncContextKey]], [[AsyncContextValue]]: _p_.[[AsyncContextValue]] }. + 1. For each Record { [[AsyncLocalKey]], [[AsyncLocalValue]] } _p_ of _previousContextMapping_, do + 1. If SameValueZero(_p_.[[AsyncLocalKey]], _AsyncLocalKey_) is *false*, then + 1. Let _q_ be the Record { [[AsyncLocalKey]]: _p_.[[AsyncLocalKey]], [[AsyncLocalValue]]: _p_.[[AsyncLocalValue]] }. 1. Append _q_ to _asyncContextMapping_. - 1. Assert: _asyncContextMapping_ does not contain a Record whose [[AsyncContextKey]] is _asyncContextKey_. - 1. Let _p_ be the Record { [[AsyncContextKey]]: _asyncContextKey_, [[AsyncContextValue]]: _value_ }. + 1. Assert: _asyncContextMapping_ does not contain a Record whose [[AsyncLocalKey]] is _AsyncLocalKey_. + 1. Let _p_ be the Record { [[AsyncLocalKey]]: _AsyncLocalKey_, [[AsyncLocalValue]]: _value_ }. 1. Append _p_ to _asyncContextMapping_. 1. AsyncContextSwap(_asyncContextMapping_). 1. Let _result_ be Completion(Call(_func_, *undefined*, _args_)). @@ -461,38 +383,38 @@

AsyncContext.prototype.run ( _value_, _func_, ..._args_ )

- -

AsyncContext.prototype.get ( )

+ +

get AsyncLocal.prototype.name

+

`AsyncLocal.prototype.name` is an accessor property whose set accessor function is *undefined*. Its get accessor function performs the following steps when called:

+ + 1. Let _asyncLocal_ be the *this* value. + 1. Perform ? RequireInternalSlot(_asyncLocal_, [[AsyncLocalKey]]). + 1. Let _asyncLocalKey_ be _asyncLocal_.[[AsyncLocalKey]]. + 1. Assert: _asyncLocalKey_ is a Symbol. + 1. Return _asyncLocalKey_.[[Description]]. + +
+ + +

AsyncLocal.prototype.get ( )

This method performs the following steps when called:

- 1. Let _asyncContext_ be the *this* value. - 1. Perform ? RequireInternalSlot(_asyncContext_, [[AsyncContextKey]]). + 1. Let _asyncLocal_ be the *this* value. + 1. Perform ? RequireInternalSlot(_asyncLocal_, [[AsyncLocalKey]]). 1. Let _agentRecord_ be the surrounding agent's Agent Record. 1. Let _asyncContextMapping_ be _agentRecord_.[[AsyncContextMapping]]. - 1. For each Record { [[AsyncContextKey]], [[AsyncContextValue]] } _p_ of _asyncContextMapping_, do - 1. If SameValueZero(_p_.[[AsyncContextKey]], _asyncContext_.[[AsyncContextKey]]) is *true*, return _p_.[[AsyncContextValue]]. - 1. Return _asyncContext_.[[AsyncContextDefaultValue]]. + 1. For each Record { [[AsyncLocalKey]], [[AsyncLocalValue]] } _p_ of _asyncContextMapping_, do + 1. If SameValueZero(_p_.[[AsyncLocalKey]], _asyncLocal_.[[AsyncLocalKey]]) is *true*, return _p_.[[AsyncLocalValue]]. + 1. Return _asyncLocal_.[[AsyncLocalDefaultValue]].
- -

get AsyncContext.prototype.name

-

`AsyncContext.prototype.name` is an accessor property whose set accessor function is *undefined*. Its get accessor function performs the following steps when called:

- - 1. Let _asyncContext_ be the *this* value. - 1. Perform ? RequireInternalSlot(_asyncContext_, [[AsyncContextKey]]). - 1. Let _asyncContextKey_ be _asyncContext_.[[AsyncContextKey]]. - 1. Assert: _asyncContextKey_ is a Symbol. - 1. Return _asyncContextKey_.[[Description]]. - -
- - -

Properties of AsyncContext Instances

-

AsyncContext instances are ordinary objects that inherit properties from the AsyncContext prototype object (the intrinsic, %AsyncContext.prototype%). AsyncContext instances are initially created with the internal slots described in .

+ +

Properties of AsyncLocal Instances

+

AsyncLocal instances are ordinary objects that inherit properties from the AsyncLocal prototype object (the intrinsic, %AsyncLocal.prototype%). AsyncLocal instances are initially created with the internal slots described in .

- +
@@ -507,24 +429,24 @@

Properties of AsyncContext Instances

- [[AsyncContextKey]] + [[AsyncLocalKey]] a Symbol - Represents the AsyncContext instance in the surrounding agent's Agent Record's [[AsyncContextMapping]]. + Represents the AsyncLocal instance in the surrounding agent's Agent Record's [[AsyncContextMapping]].
- [[AsyncContextDefaultValue]] + [[AsyncLocalDefaultValue]] an ECMAScript language value - The default value of the AsyncContext instance when no entry is found in the mapping. + The default value of the AsyncLocal instance when no entry is found in the mapping.
From 612676c37629a05f447101f1d1ea0b2315487876 Mon Sep 17 00:00:00 2001 From: Chengzhong Wu Date: Sat, 6 May 2023 15:30:48 +0800 Subject: [PATCH 02/10] fixup! --- spec.html | 130 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 90 insertions(+), 40 deletions(-) diff --git a/spec.html b/spec.html index 9a15121..015c236 100644 --- a/spec.html +++ b/spec.html @@ -7,6 +7,60 @@ contributors: Chengzhong Wu, Justin Ridgewell + +

ECMAScript Data Types and Values

+ + +

ECMAScript Specification Types

+ + + +

The Async Context Mapping Record Specification Type

+

The Async Context Mapping Record type is used to represent an AsyncLocal value mapping in the surrounding Agent's [[AsyncContextMapping]].

+

A Async Context Mapping Record's fields are defined by .

+ + + + + + + + + + + + + + + + + + +
+ Field Name + + Value + + Meaning +
+ [[AsyncContextKey]] + + an AsyncLocal instance + + The name of the AsyncLocal instance. +
+ [[AsyncContextValue]] + + an ECMAScript language value + + The default value of the AsyncLocal instance when no entry is found in the mapping. +
+
+
+
+
+
+

Executable Code and Execution Contexts

@@ -65,10 +119,10 @@

Agents

[[AsyncContextMapping]] - a List of Records with fields [[AsyncLocalKey]] (a Symbol) and [[AsyncLocalValue]] (an ECMAScript language value) + a List of Async Context Mapping Records - A map from the AsyncContext's key symbol to the saved ECMAScript language value. Every Record in the List contains a unique [[AsyncLocalKey]]. + A map from the AsyncContext's key symbol to the saved ECMAScript language value. Every Record in the List contains a unique [[AsyncContextKey]]. @@ -110,10 +164,10 @@

JobCallback Records

[[AsyncContextSnapshot]] - a List of Records with fields [[AsyncLocalKey]] (a Symbol) and [[AsyncLocalValue]] (an ECMAScript language value) + a List of Async Context Mapping Records - A map from the AsyncContext's key symbol to the saved ECMAScript language value. Every Record in the List contains a unique [[AsyncLocalKey]]. + A map from the AsyncContext's key symbol to the saved ECMAScript language value. Every Record in the List contains a unique [[AsyncContextKey]]. @@ -147,8 +201,8 @@

The default implementation of HostMakeJobCallback performs the following steps when called:

1. Return the JobCallback Record { [[Callback]]: _callback_, [[HostDefined]]: ~empty~ }. - 1. Let _snapshot_ be AsyncContextSnapshot(). - 1. Return the JobCallback Record { [[Callback]]: _callback_, [[AsyncContextSnapshot]]: _snapshot_, [[HostDefined]]: ~empty~ }. + 1. Let _snapshotMapping_ be AsyncContextSnapshot(). + 1. Return the JobCallback Record { [[Callback]]: _callback_, [[AsyncContextSnapshot]]: _snapshotMapping_, [[HostDefined]]: ~empty~ }.

ECMAScript hosts that are not web browsers must use the default implementation of HostMakeJobCallback.

@@ -189,9 +243,9 @@

-

Control Abstraction Objects

+

AsyncSnapshot Objects

@@ -200,7 +254,7 @@

AsyncSnapshot Abstract Operations

AsyncContextSnapshot ( - ): a List of Records with fields [[AsyncLocalKey]] (a Symbol) and [[AsyncLocalValue]] (an ECMAScript language value) + ): a List of Async Context Mapping Records

description
@@ -215,17 +269,17 @@

AsyncContextSwap ( - _snapshot_: a List of Records with fields [[AsyncLocalKey]] (a Symbol) and [[AsyncLocalValue]] (an ECMAScript language value) - ): a List of Records with fields [[AsyncLocalKey]] (a Symbol) and [[AsyncLocalValue]] (an ECMAScript language value) + _snapshotMapping_: a List of Async Context Mapping Records + ): a List of Async Context Mapping Records

description
-
It is used to swap the surrounding agent's Agent Record's [[AsyncContextMapping]] with the _snapshot_.
+
It is used to swap the surrounding agent's Agent Record's [[AsyncContextMapping]] with the _snapshotMapping_.
1. Let _agentRecord_ be the surrounding agent's Agent Record. 1. Let _asyncContextMapping_ be _agentRecord_.[[AsyncContextMapping]]. - 1. Set _agentRecord_.[[AsyncContextMapping]] to _snapshot_. + 1. Set _agentRecord_.[[AsyncContextMapping]] to _snapshotMapping_. 1. Return _asyncContextMapping_.
@@ -248,9 +302,9 @@

AsyncSnapshot ( )

1. If NewTarget is *undefined*, throw a *TypeError* exception. - 1. Let _snapshot_ be AsyncContextSnapshot(). + 1. Let _snapshotMapping_ be AsyncContextSnapshot(). 1. Let _asyncSnapshot_ be ? OrdinaryCreateFromConstructor(NewTarget, *"%AsyncSnapshot.prototype%"*, « [[AsyncSnapshotMapping]] »). - 1. Set _asyncSnapshot_.[[AsyncSnapshotMapping]] to _snapshot_. + 1. Set _asyncSnapshot_.[[AsyncSnapshotMapping]] to _snapshotMapping_. 1. Return _asyncSnapshot_. @@ -272,11 +326,11 @@

AsyncSnapshot.prototype.run ( _func_, ..._args_ )

1. Let _asyncSnapshot_ be the *this* value. 1. Perform ? RequireInternalSlot(_asyncSnapshot_, [[AsyncSnapshotMapping]]). - 1. Let _snapshot_ be _asyncSnapshot_.[[AsyncSnapshotMapping]]. - 1. Let _contextMapping_ be AsyncContextSnapshot(). - 1. AsyncContextSwap(_snapshot_). + 1. Let _snapshotMapping_ be _asyncSnapshot_.[[AsyncSnapshotMapping]]. + 1. Let _previousContextMapping_ be AsyncContextSnapshot(). + 1. AsyncContextSwap(_snapshotMapping_). 1. Let _result_ be Completion(Call(_func_, *undefined*, _args_)). - 1. AsyncContextSwap(_contextMapping_). + 1. AsyncContextSwap(_previousContextMapping_). 1. Return _result_. @@ -304,7 +358,7 @@

Properties of AsyncSnapshot Instances

[[AsyncSnapshotMapping]] - a List of Records with fields [[AsyncLocalKey]] (a Symbol) and [[AsyncLocalValue]] (an ECMAScript language value) + a List of Async Context Mapping Records Represents the snapshotted surrounding agent's Agent Record's [[AsyncContextMapping]] of the AsyncSnapshot instance. @@ -342,8 +396,8 @@

AsyncLocal ( _options_ )

1. Else, 1. Let _nameStr_ be the empty String. 1. Let _defaultValue_ be *undefined*. - 1. Let _asyncLocal_ be ? OrdinaryCreateFromConstructor(NewTarget, *"%AsyncLocal.prototype%"*, « [[AsyncLocalKey]], [[AsyncLocalDefaultValue]] »). - 1. Set _asyncLocal_.[[AsyncLocalKey]] to a new Symbol whose [[Description]] is _nameStr_. + 1. Let _asyncLocal_ be ? OrdinaryCreateFromConstructor(NewTarget, *"%AsyncLocal.prototype%"*, « [[AsyncLocalName]], [[AsyncLocalDefaultValue]] »). + 1. Set _asyncLocal_.[[AsyncLocalName]] to _nameStr_. 1. Set _asyncLocal_.[[AsyncLocalDefaultValue]] to _defaultValue_. 1. Return _asyncLocal_. @@ -365,16 +419,15 @@

AsyncLocal.prototype.run ( _value_, _func_, ..._args_ )

This method performs the following steps when called:

1. Let _asyncLocal_ be the *this* value. - 1. Perform ? RequireInternalSlot(_asyncLocal_, [[AsyncLocalKey]]). - 1. Let _AsyncLocalKey_ be _asyncLocal_.[[AsyncLocalKey]]. + 1. Perform ? RequireInternalSlot(_asyncLocal_, [[AsyncLocalName]]). 1. Let _previousContextMapping_ be AsyncContextSnapshot(). 1. Let _asyncContextMapping_ be a new empty List. - 1. For each Record { [[AsyncLocalKey]], [[AsyncLocalValue]] } _p_ of _previousContextMapping_, do - 1. If SameValueZero(_p_.[[AsyncLocalKey]], _AsyncLocalKey_) is *false*, then - 1. Let _q_ be the Record { [[AsyncLocalKey]]: _p_.[[AsyncLocalKey]], [[AsyncLocalValue]]: _p_.[[AsyncLocalValue]] }. + 1. For each Async Context Mapping Record _p_ of _previousContextMapping_, do + 1. If SameValueZero(_p_.[[AsyncContextKey]], _asyncLocal_) is *false*, then + 1. Let _q_ be the Async Context Mapping Record { [[AsyncContextKey]]: _p_.[[AsyncContextKey]], [[AsyncContextValue]]: _p_.[[AsyncContextValue]] }. 1. Append _q_ to _asyncContextMapping_. - 1. Assert: _asyncContextMapping_ does not contain a Record whose [[AsyncLocalKey]] is _AsyncLocalKey_. - 1. Let _p_ be the Record { [[AsyncLocalKey]]: _AsyncLocalKey_, [[AsyncLocalValue]]: _value_ }. + 1. Assert: _asyncContextMapping_ does not contain an Async Context Mapping Record whose [[AsyncContextKey]] is _asyncLocal_. + 1. Let _p_ be the Async Context Mapping Record { [[AsyncContextKey]]: _asyncLocal_, [[AsyncContextValue]]: _value_ }. 1. Append _p_ to _asyncContextMapping_. 1. AsyncContextSwap(_asyncContextMapping_). 1. Let _result_ be Completion(Call(_func_, *undefined*, _args_)). @@ -388,10 +441,8 @@

get AsyncLocal.prototype.name

`AsyncLocal.prototype.name` is an accessor property whose set accessor function is *undefined*. Its get accessor function performs the following steps when called:

1. Let _asyncLocal_ be the *this* value. - 1. Perform ? RequireInternalSlot(_asyncLocal_, [[AsyncLocalKey]]). - 1. Let _asyncLocalKey_ be _asyncLocal_.[[AsyncLocalKey]]. - 1. Assert: _asyncLocalKey_ is a Symbol. - 1. Return _asyncLocalKey_.[[Description]]. + 1. Perform ? RequireInternalSlot(_asyncLocal_, [[AsyncLocalName]]). + 1. Return _asyncLocal_.[[AsyncLocalName]]. @@ -400,11 +451,11 @@

AsyncLocal.prototype.get ( )

This method performs the following steps when called:

1. Let _asyncLocal_ be the *this* value. - 1. Perform ? RequireInternalSlot(_asyncLocal_, [[AsyncLocalKey]]). + 1. Perform ? RequireInternalSlot(_asyncLocal_, [[AsyncLocalName]]). 1. Let _agentRecord_ be the surrounding agent's Agent Record. 1. Let _asyncContextMapping_ be _agentRecord_.[[AsyncContextMapping]]. - 1. For each Record { [[AsyncLocalKey]], [[AsyncLocalValue]] } _p_ of _asyncContextMapping_, do - 1. If SameValueZero(_p_.[[AsyncLocalKey]], _asyncLocal_.[[AsyncLocalKey]]) is *true*, return _p_.[[AsyncLocalValue]]. + 1. For each Async Context Mapping Record _p_ of _asyncContextMapping_, do + 1. If SameValueZero(_p_.[[AsyncContextKey]], _asyncLocal_) is *true*, return _p_.[[AsyncContextValue]]. 1. Return _asyncLocal_.[[AsyncLocalDefaultValue]]. @@ -429,13 +480,13 @@

Properties of AsyncLocal Instances

- [[AsyncLocalKey]] + [[AsyncLocalName]] - a Symbol + a String - Represents the AsyncLocal instance in the surrounding agent's Agent Record's [[AsyncContextMapping]]. + The name of the AsyncLocal instance. @@ -453,6 +504,5 @@

Properties of AsyncLocal Instances

+ - - From 6a6be2fd5ea61d66a754a138aad6752cfdb01fe1 Mon Sep 17 00:00:00 2001 From: Chengzhong Wu Date: Sat, 6 May 2023 15:37:06 +0800 Subject: [PATCH 03/10] fixup! README.md --- README.md | 38 +++++++++++++++++++------------------- SCOPING.md | 28 ++++++++++++++-------------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 5d5c751..af8c9b0 100644 --- a/README.md +++ b/README.md @@ -185,42 +185,42 @@ only access the value associated with an `AsyncLocal` instance if you have access to that instance. ```typescript -const asyncLocal = new AsyncLocal(); +const local = new AsyncLocal(); // Sets the current value to 'top', and executes the `main` function. -asyncLocal.run("top", main); +local.run("top", main); function main() { // AsyncLocal is maintained through other platform queueing. setTimeout(() => { - console.log(asyncLocal.get()); // => 'top' + console.log(local.get()); // => 'top' - asyncLocal.run("A", () => { - console.log(asyncLocal.get()); // => 'A' + local.run("A", () => { + console.log(local.get()); // => 'A' setTimeout(() => { - console.log(asyncLocal.get()); // => 'A' + console.log(local.get()); // => 'A' }, randomTimeout()); }); }, randomTimeout()); // AsyncLocal runs can be nested. - asyncLocal.run("B", () => { - console.log(asyncLocal.get()); // => 'B' + local.run("B", () => { + console.log(local.get()); // => 'B' setTimeout(() => { - console.log(asyncLocal.get()); // => 'B' + console.log(local.get()); // => 'B' }, randomTimeout()); }); // AsyncLocal was restored after the previous run. - console.log(asyncLocal.get()); // => 'top' + console.log(local.get()); // => 'top' // Captures the state of all AsyncLocal's at this moment. const snapshotDuringTop = new AsyncSnapshot(); - asyncLocal.run("C", () => { - console.log(asyncLocal.get()); // => 'C' + local.run("C", () => { + console.log(local.get()); // => 'C' // The snapshotDuringTop will restore all AsyncLocal to their snapshot // state and invoke the wrapped function. We pass a function which it will @@ -228,7 +228,7 @@ function main() { snapshotDuringTop.run(() => { // Despite being lexically nested inside 'C', the snapshot restored us to // to the 'top' state. - console.log(asyncLocal.get()); // => 'top' + console.log(local.get()); // => 'top' }); }); } @@ -312,7 +312,7 @@ tracing span doesn't need to be manually passing around by usercodes. ```typescript // tracer.js -const asyncLocal = new AsyncLocal(); +const local = new AsyncLocal(); export function run(cb) { // (a) const span = { @@ -320,12 +320,12 @@ export function run(cb) { traceId: randomUUID(), spanId: randomUUID(), }; - asyncLocal.run(span, cb); + local.run(span, cb); } export function end() { // (b) - const span = asyncLocal.get(); + const span = local.get(); span?.endTime = Date.now(); } ``` @@ -367,14 +367,14 @@ scheduled with the same priority. ```typescript const scheduler = { - asyncLocal: new AsyncLocal(), + local: new AsyncLocal(), postTask(task, options) { // In practice, the task execution may be deferred. // Here we simply run the task immediately. - return this.asyncLocal.run({ priority: options.priority }, task); + return this.local.run({ priority: options.priority }, task); }, currentTask() { - return this.asyncLocal.get() ?? { priority: "default" }; + return this.local.get() ?? { priority: "default" }; }, }; diff --git a/SCOPING.md b/SCOPING.md index 33a742c..8a533e4 100644 --- a/SCOPING.md +++ b/SCOPING.md @@ -29,19 +29,19 @@ value inside an `AsyncLocal` without explicit access to the `AsyncLocal` instanc itself. ```typescript -const asyncLocal = new AsyncLocal(); +const local = new AsyncLocal(); -asyncLocal.run(1, f); -console.log(asyncLocal.get()); // => undefined +local.run(1, f); +console.log(local.get()); // => undefined function g() { - console.log(asyncLocal.get()); // => 1 + console.log(local.get()); // => 1 } function f() { - // Intentionally named the same "asyncLocal" - const asyncLocal = new AsyncLocal(); - asyncLocal.run(2, g); + // Intentionally named the same "local" + const local = new AsyncLocal(); + local.run(2, g); } ``` @@ -50,18 +50,18 @@ ability to change the value of that variable. You must have direct access to it in order to affect it. ```typescript -const asyncLocal = new AsyncLocal(); +const local = new AsyncLocal(); -asyncLocal.run(1, f); +local.run(1, f); -console.log(asyncLocal.get()); // => undefined; +console.log(local.get()); // => undefined; function f() { - const asyncLocal = new AsyncLocal(); - asyncLocal.run(2, g); + const local = new AsyncLocal(); + local.run(2, g); function g() { - console.log(asyncLocal.get()); // => 2; + console.log(local.get()); // => 2; } } ``` @@ -119,5 +119,5 @@ that can operate across sync/async execution should be no different. There are no differences regarding naming scope of `AsyncLocal` compared to regular JavaScript variables. Only code with direct access to `AsyncLocal` instances can modify the value, and only for code execution nested inside a new -`asyncLocal.run()`. Further, the capability to modify a local variable which you +`local.run()`. Further, the capability to modify a local variable which you have direct access to is already possible in sync code execution. From 8bf37a53a1b68fc5a8a4ea69170f1773a68cd56d Mon Sep 17 00:00:00 2001 From: legendecas Date: Tue, 30 May 2023 22:47:37 +0800 Subject: [PATCH 04/10] fixup! --- README.md | 6 +++--- spec.html | 25 +++++++++++++------------ 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index af8c9b0..32cf6b8 100644 --- a/README.md +++ b/README.md @@ -172,7 +172,7 @@ interface AsyncLocalOptions { class AsyncSnapshot { constructor(); - run(fn: (...args: any[]) => R, ...args: any[]): R; + restore(fn: (...args: any[]) => R, ...args: any[]): R; } ``` @@ -225,7 +225,7 @@ function main() { // The snapshotDuringTop will restore all AsyncLocal to their snapshot // state and invoke the wrapped function. We pass a function which it will // invoke. - snapshotDuringTop.run(() => { + snapshotDuringTop.restore(() => { // Despite being lexically nested inside 'C', the snapshot restored us to // to the 'top' state. console.log(local.get()); // => 'top' @@ -249,7 +249,7 @@ export function enqueueCallback(cb: () => void) { // Each callback is stored with the context at which it was enqueued. const snapshot = new AsyncSnapshot(); queue.push(() => { - snapshot.run(cb); + snapshot.restore(cb); }); } diff --git a/spec.html b/spec.html index 015c236..858effe 100644 --- a/spec.html +++ b/spec.html @@ -40,7 +40,7 @@

The Async Context Mapping Record Specification Type

an AsyncLocal instance - The name of the AsyncLocal instance. + The AsyncLocal instance as the key in the mapping. @@ -51,7 +51,7 @@

The Async Context Mapping Record Specification Type

an ECMAScript language value - The default value of the AsyncLocal instance when no entry is found in the mapping. + The value of the AsyncLocal instance in the mapping. @@ -122,7 +122,7 @@

Agents

a List of Async Context Mapping Records - A map from the AsyncContext's key symbol to the saved ECMAScript language value. Every Record in the List contains a unique [[AsyncContextKey]]. + A map from the AsyncLocal instances to the saved ECMAScript language value. Every Record in the List contains a unique [[AsyncContextKey]]. The map is initially empty. @@ -167,7 +167,7 @@

JobCallback Records

a List of Async Context Mapping Records - A map from the AsyncContext's key symbol to the saved ECMAScript language value. Every Record in the List contains a unique [[AsyncContextKey]]. + A map from the AsyncLocal instances to the saved ECMAScript language value. Every Record in the List contains a unique [[AsyncContextKey]]. @@ -320,8 +320,8 @@

Properties of the AsyncSnapshot Prototype Object

  • does not have any of the other internal slots of AsyncSnapshot instances.
  • - -

    AsyncSnapshot.prototype.run ( _func_, ..._args_ )

    + +

    AsyncSnapshot.prototype.restore ( _func_, ..._args_ )

    This method performs the following steps when called:

    1. Let _asyncSnapshot_ be the *this* value. @@ -380,7 +380,7 @@

    The AsyncLocal Constructor

  • is the initial value of the *"AsyncLocal"* property of the global object.
  • creates and initializes a new AsyncLocal when called as a constructor.
  • is not intended to be called as a function and will throw an exception when called in that manner.
  • -
  • may be used as the value in an `extends` clause of a class definition. Subclass constructors that intend to inherit the specified AsyncLocal behaviour must include a `super` call to the AsyncLocal constructor to create and initialize the subclass instance with the internal state necessary to support the `AsyncLocal` and `AsyncLocal.prototype` built-in methods.
  • +
  • may be used as the value in an `extends` clause of a class definition. Subclass constructors that intend to inherit the specified AsyncLocal behaviour must include a `super` call to the AsyncLocal constructor to create and initialize the subclass instance with the internal state necessary to support the `AsyncLocal.prototype` built-in methods.
  • @@ -389,13 +389,14 @@

    AsyncLocal ( _options_ )

    1. If NewTarget is *undefined*, throw a *TypeError* exception. + 1. Let _nameStr_ be the empty String. + 1. Let _defaultValue_ be *undefined*. 1. If _options_ is an Object, then - 1. Let _name_ be ? Get(_options_, *"name"*). - 1. Let _nameStr_ be ? ToString(_name_). + 1. Let _namePresent_ be ? HasProperty(_options_, *"name"*). + 1. If _namePresent_ is *true*, then + 1. Let _name_ be ? Get(_options_, *"name"*). + 1. Let _nameStr_ be ? ToString(_name_). 1. Let _defaultValue_ be ? Get(_options_, *"defaultValue"*). - 1. Else, - 1. Let _nameStr_ be the empty String. - 1. Let _defaultValue_ be *undefined*. 1. Let _asyncLocal_ be ? OrdinaryCreateFromConstructor(NewTarget, *"%AsyncLocal.prototype%"*, « [[AsyncLocalName]], [[AsyncLocalDefaultValue]] »). 1. Set _asyncLocal_.[[AsyncLocalName]] to _nameStr_. 1. Set _asyncLocal_.[[AsyncLocalDefaultValue]] to _defaultValue_. From 2f4e2d06a5daf56e58707016ebe21e680caa608a Mon Sep 17 00:00:00 2001 From: Chengzhong Wu Date: Mon, 12 Jun 2023 20:50:08 +0800 Subject: [PATCH 05/10] fixup! namespace AsyncContext --- README.md | 100 +++++++++++++------------- SCOPING.md | 69 +++++++++--------- spec.html | 201 ++++++++++++++++++++++++++++++----------------------- 3 files changed, 200 insertions(+), 170 deletions(-) diff --git a/README.md b/README.md index 32cf6b8..3cef8ad 100644 --- a/README.md +++ b/README.md @@ -150,85 +150,87 @@ Non-goals: # Proposed Solution -`AsyncLocal` are designed as a value store for context propagation across +`AsyncContext.Variable` are designed as a value store for context propagation across logically-connected sync/async code execution. ```typescript -class AsyncLocal { - constructor(options: AsyncLocalOptions); +namespace AsyncContext { + class Variable { + constructor(options: AsyncVariableOptions); - get name(): string; + get name(): string; - run(value: T, fn: () => R): R; + run(value: T, fn: () => R): R; - get(): T | undefined; -} + get(): T | undefined; + } -interface AsyncLocalOptions { - name?: string; - defaultValue?: T; -} + interface AsyncVariableOptions { + name?: string; + defaultValue?: T; + } -class AsyncSnapshot { - constructor(); + class Snapshot { + constructor(); - restore(fn: (...args: any[]) => R, ...args: any[]): R; + restore(fn: (...args: any[]) => R, ...args: any[]): R; + } } ``` -`AsyncLocal.prototype.run()` and `AsyncLocal.prototype.get()` sets and gets -the current value of an async execution flow. `AsyncSnapshot` allows you -to opaquely capture the current value of all `AsyncLocal`s and execute a +`AsyncContext.Variable.prototype.run()` and `AsyncContext.Variable.prototype.get()` sets and gets +the current value of an async execution flow. `AsyncContext.Snapshot` allows you +to opaquely capture the current value of all `AsyncContext.Variable`s and execute a function at a later time with as if those values were still the current values -(a snapshot and restore). Note that even with `AsyncSnapshot`, you can -only access the value associated with an `AsyncLocal` instance if you have +(a snapshot and restore). Note that even with `AsyncContext.Snapshot`, you can +only access the value associated with an `AsyncContext.Variable` instance if you have access to that instance. ```typescript -const local = new AsyncLocal(); +const asyncVar = new AsyncContext.Variable(); // Sets the current value to 'top', and executes the `main` function. -local.run("top", main); +asyncVar.run("top", main); function main() { - // AsyncLocal is maintained through other platform queueing. + // AsyncContext.Variable is maintained through other platform queueing. setTimeout(() => { - console.log(local.get()); // => 'top' + console.log(asyncVar.get()); // => 'top' - local.run("A", () => { - console.log(local.get()); // => 'A' + asyncVar.run("A", () => { + console.log(asyncVar.get()); // => 'A' setTimeout(() => { - console.log(local.get()); // => 'A' + console.log(asyncVar.get()); // => 'A' }, randomTimeout()); }); }, randomTimeout()); - // AsyncLocal runs can be nested. - local.run("B", () => { - console.log(local.get()); // => 'B' + // AsyncContext.Variable runs can be nested. + asyncVar.run("B", () => { + console.log(asyncVar.get()); // => 'B' setTimeout(() => { - console.log(local.get()); // => 'B' + console.log(asyncVar.get()); // => 'B' }, randomTimeout()); }); - // AsyncLocal was restored after the previous run. - console.log(local.get()); // => 'top' + // AsyncContext.Variable was restored after the previous run. + console.log(asyncVar.get()); // => 'top' - // Captures the state of all AsyncLocal's at this moment. - const snapshotDuringTop = new AsyncSnapshot(); + // Captures the state of all AsyncContext.Variable's at this moment. + const snapshotDuringTop = new AsyncContext.Snapshot(); - local.run("C", () => { - console.log(local.get()); // => 'C' + asyncVar.run("C", () => { + console.log(asyncVar.get()); // => 'C' - // The snapshotDuringTop will restore all AsyncLocal to their snapshot + // The snapshotDuringTop will restore all AsyncContext.Variable to their snapshot // state and invoke the wrapped function. We pass a function which it will // invoke. snapshotDuringTop.restore(() => { // Despite being lexically nested inside 'C', the snapshot restored us to // to the 'top' state. - console.log(local.get()); // => 'top' + console.log(asyncVar.get()); // => 'top' }); }); } @@ -238,7 +240,7 @@ function randomTimeout() { } ``` -`AsyncSnapshot` is useful for implementing APIs that logically "schedule" a +`AsyncContext.Snapshot` is useful for implementing APIs that logically "schedule" a callback, so the callback will be called with the context that it logically belongs to, regardless of the context under which it actually runs: @@ -247,7 +249,7 @@ let queue = []; export function enqueueCallback(cb: () => void) { // Each callback is stored with the context at which it was enqueued. - const snapshot = new AsyncSnapshot(); + const snapshot = new AsyncContext.Snapshot(); queue.push(() => { snapshot.restore(cb); }); @@ -264,7 +266,7 @@ runWhenIdle(() => { ``` > Note: There are controversial thought on the dynamic scoping and -> `AsyncLocal`, checkout [SCOPING.md][] for more details. +> `AsyncContext.Variable`, checkout [SCOPING.md][] for more details. ## Use cases @@ -303,7 +305,7 @@ A detailed example usecase can be found [here](./USE-CASES.md) ## Determine the initiator of a task Application monitoring tools like OpenTelemetry save their tracing spans in the -`AsyncLocal` and retrieve the span when they need to determine what started +`AsyncContext.Variable` and retrieve the span when they need to determine what started this chain of interaction. These libraries can not intrude the developer APIs for seamless monitoring. The @@ -312,7 +314,7 @@ tracing span doesn't need to be manually passing around by usercodes. ```typescript // tracer.js -const local = new AsyncLocal(); +const asyncVar = new AsyncContext.Variable(); export function run(cb) { // (a) const span = { @@ -320,12 +322,12 @@ export function run(cb) { traceId: randomUUID(), spanId: randomUUID(), }; - local.run(span, cb); + asyncVar.run(span, cb); } export function end() { // (b) - const span = local.get(); + const span = asyncVar.get(); span?.endTime = Date.now(); } ``` @@ -361,20 +363,20 @@ concurrent multi-tracking. ## Transitive task attribution -User tasks can be scheduled with attributions. With `AsyncLocal`, task +User tasks can be scheduled with attributions. With `AsyncContext.Variable`, task attributions are propagated in the async task flow and sub-tasks can be scheduled with the same priority. ```typescript const scheduler = { - local: new AsyncLocal(), + asyncVar: new AsyncContext.Variable(), postTask(task, options) { // In practice, the task execution may be deferred. // Here we simply run the task immediately. - return this.local.run({ priority: options.priority }, task); + return this.asyncVar.run({ priority: options.priority }, task); }, currentTask() { - return this.local.get() ?? { priority: "default" }; + return this.asyncVar.get() ?? { priority: "default" }; }, }; diff --git a/SCOPING.md b/SCOPING.md index 8a533e4..c8e99d6 100644 --- a/SCOPING.md +++ b/SCOPING.md @@ -1,8 +1,9 @@ -# Scoping of AsyncLocal +# Scoping of AsyncContext.Variable -The major concerns of `AsyncLocal` advancing to Stage 1 of TC39 proposal +The major concerns of `AsyncContext.Variable` advancing to Stage 1 of TC39 proposal process is that there are potential dynamic scoping of the semantics of -`AsyncLocal`. This document is about defining the scoping of `AsyncLocal`. +`AsyncContext.Variable`. This document is about defining the scoping of +`AsyncContext.Variable`. ### Dynamic Scoping @@ -22,61 +23,61 @@ $ echo $x # does this print 1, or 2? 1 ``` -However, the naming scope of an `AsyncLocal` is identical to a regular variable +However, the naming scope of an `AsyncContext.Variable` is identical to a regular variable in JavaScript. Since JavaScript variables are lexically scoped, the naming of -`AsyncLocal` instances are lexically scoped too. It is not possible to access a -value inside an `AsyncLocal` without explicit access to the `AsyncLocal` instance +`AsyncContext.Variable` instances are lexically scoped too. It is not possible to access a +value inside an `AsyncContext.Variable` without explicit access to the `AsyncContext.Variable` instance itself. ```typescript -const local = new AsyncLocal(); +const asyncVar = new AsyncContext.Variable(); -local.run(1, f); -console.log(local.get()); // => undefined +asyncVar.run(1, f); +console.log(asyncVar.get()); // => undefined function g() { - console.log(local.get()); // => 1 + console.log(asyncVar.get()); // => 1 } function f() { - // Intentionally named the same "local" - const local = new AsyncLocal(); - local.run(2, g); + // Intentionally named the same "asyncVar" + const asyncVar = new AsyncContext.Variable(); + asyncVar.run(2, g); } ``` -Hence, knowing the name of an `AsyncLocal` variable does not give you the +Hence, knowing the name of an `AsyncContext.Variable` variable does not give you the ability to change the value of that variable. You must have direct access to it in order to affect it. ```typescript -const local = new AsyncLocal(); +const asyncVar = new AsyncContext.Variable(); -local.run(1, f); +asyncVar.run(1, f); -console.log(local.get()); // => undefined; +console.log(asyncVar.get()); // => undefined; function f() { - const local = new AsyncLocal(); - local.run(2, g); + const asyncVar = new AsyncContext.Variable(); + asyncVar.run(2, g); function g() { - console.log(local.get()); // => 2; + console.log(asyncVar.get()); // => 2; } } ``` ### Dynamic Scoping: dependency on caller -One argument on the dynamic scoping is that the values in `AsyncLocal` can be +One argument on the dynamic scoping is that the values in `AsyncContext.Variable` can be changed depending on which the caller is. -However, the definition of whether the value of an `AsyncLocal` can be changed +However, the definition of whether the value of an `AsyncContext.Variable` can be changed has the same meaning with a regular JavaScript variable: anyone with direct access to a variable has the ability to change the variable. ```typescript -class SyncLocal { +class SyncVariable { #current; get() { @@ -94,30 +95,30 @@ class SyncLocal { } } -const syncLocal = new SyncLocal(); +const syncVar = new SyncVariable(); -syncLocal.run(1, f); +syncVar.run(1, f); -console.log(syncLocal.get()); // => undefined; +console.log(syncVar.get()); // => undefined; function g() { - console.log(syncLocal.get()); // => 1 + console.log(syncVar.get()); // => 1 } function f() { - // Intentionally named the same "syncLocal" - const syncLocal = new AsyncLocal(); - syncLocal.run(2, g); + // Intentionally named the same "syncVar" + const syncVar = new AsyncContext.Variable(); + syncVar.run(2, g); } ``` -If this userland `SyncLocal` is acceptable, than adding an `AsyncLocal` +If this userland `SyncVariable` is acceptable, than adding an `AsyncContext.Variable` that can operate across sync/async execution should be no different. ### Summary -There are no differences regarding naming scope of `AsyncLocal` compared to -regular JavaScript variables. Only code with direct access to `AsyncLocal` +There are no differences regarding naming scope of `AsyncContext.Variable` compared to +regular JavaScript variables. Only code with direct access to `AsyncContext.Variable` instances can modify the value, and only for code execution nested inside a new -`local.run()`. Further, the capability to modify a local variable which you +`asyncVar.run()`. Further, the capability to modify an AsyncVariable which you have direct access to is already possible in sync code execution. diff --git a/spec.html b/spec.html index 858effe..0d330bb 100644 --- a/spec.html +++ b/spec.html @@ -16,8 +16,8 @@

    ECMAScript Specification Types

    The Async Context Mapping Record Specification Type

    -

    The Async Context Mapping Record type is used to represent an AsyncLocal value mapping in the surrounding Agent's [[AsyncContextMapping]].

    -

    A Async Context Mapping Record's fields are defined by .

    +

    The Async Context Mapping Record type is used to represent an AsyncContext.Variable value mapping in the surrounding Agent's [[AsyncContextMapping]].

    +

    An Async Context Mapping Record's fields are defined by .

    @@ -37,10 +37,10 @@

    The Async Context Mapping Record Specification Type

    [[AsyncContextKey]] @@ -51,7 +51,7 @@

    The Async Context Mapping Record Specification Type

    an ECMAScript language value
    - an AsyncLocal instance + an AsyncContext.Variable instance - The AsyncLocal instance as the key in the mapping. + The AsyncContext.Variable instance as the key in the mapping.
    - The value of the AsyncLocal instance in the mapping. + The value of the AsyncContext.Variable instance in the mapping.
    @@ -122,7 +122,7 @@

    Agents

    a List of Async Context Mapping Records - A map from the AsyncLocal instances to the saved ECMAScript language value. Every Record in the List contains a unique [[AsyncContextKey]]. The map is initially empty. + A map from the AsyncContext.Variable instances to the saved ECMAScript language value. Every Record in the List contains a unique [[AsyncContextKey]]. The map is initially empty. @@ -167,7 +167,7 @@

    JobCallback Records

    a List of Async Context Mapping Records - A map from the AsyncLocal instances to the saved ECMAScript language value. Every Record in the List contains a unique [[AsyncContextKey]]. + A map from the AsyncContext.Variable instances to the saved ECMAScript language value. Every Record in the List contains a unique [[AsyncContextKey]]. @@ -246,12 +246,23 @@

    Control Abstraction Objects

    - -

    AsyncSnapshot Objects

    - -

    AsyncSnapshot Abstract Operations

    - + +

    The AsyncContext Object

    +

    The AsyncContext object:

    +
      +
    • is the intrinsic object %AsyncContext%.
    • +
    • is the initial value of the *"AsyncContext"* property of the global object.
    • +
    • is an ordinary object.
    • +
    • has a [[Prototype]] internal slot whose value is %Object.prototype%.
    • +
    • is not a function object.
    • +
    • does not have a [[Construct]] internal method; it cannot be used as a constructor with the `new` operator.
    • +
    • does not have a [[Call]] internal method; it cannot be invoked as a function.
    • +
    + + +

    AsyncContext Abstract Operations

    +

    AsyncContextSnapshot ( ): a List of Async Context Mapping Records @@ -266,7 +277,7 @@

    - +

    AsyncContextSwap ( _snapshotMapping_: a List of Async Context Mapping Records @@ -285,50 +296,66 @@

    - -

    The AsyncSnapshot Constructor

    -

    The AsyncSnapshot constructor:

    + +

    Constructor Properties of the AsyncContext Object

    + + +

    AsyncContext.Snapshot ( . . . )

    +

    See .

    +
    + + +

    AsyncContext.Variable ( . . . )

    +

    See .

    +
    +
    +
    + + +

    AsyncContext.Snapshot Objects

    + + +

    The AsyncContext.Snapshot Constructor

    +

    The AsyncContext.Snapshot constructor:

      -
    • is %AsyncSnapshot%.
    • -
    • is the initial value of the *"AsyncSnapshot"* property of the global object.
    • -
    • creates and initializes a new AsyncSnapshot when called as a constructor.
    • +
    • is %AsyncContext.Snapshot%.
    • +
    • is the initial value of the *"AsyncContext.Snapshot"* property of the %AsyncContext% object.
    • +
    • creates and initializes a new AsyncContext.Snapshot when called as a constructor.
    • is not intended to be called as a function and will throw an exception when called in that manner.
    • -
    • may be used as the value in an `extends` clause of a class definition. Subclass constructors that intend to inherit the specified AsyncSnapshot behaviour must include a `super` call to the AsyncSnapshot constructor to create and initialize the subclass instance with the internal state necessary to support the `AsyncSnapshot` and `AsyncSnapshot.prototype` built-in methods.
    • +
    • may be used as the value in an `extends` clause of a class definition. Subclass constructors that intend to inherit the specified AsyncContext.Snapshot behaviour must include a `super` call to the AsyncContext.Snapshot constructor to create and initialize the subclass instance with the internal state necessary to support the `AsyncContext.Snapshot` and `AsyncContext.Snapshot.prototype` built-in methods.
    - -

    AsyncSnapshot ( )

    + +

    AsyncContext.Snapshot ( )

    This function performs the following steps when called:

    1. If NewTarget is *undefined*, throw a *TypeError* exception. 1. Let _snapshotMapping_ be AsyncContextSnapshot(). - 1. Let _asyncSnapshot_ be ? OrdinaryCreateFromConstructor(NewTarget, *"%AsyncSnapshot.prototype%"*, « [[AsyncSnapshotMapping]] »). + 1. Let _asyncSnapshot_ be ? OrdinaryCreateFromConstructor(NewTarget, *"%AsyncContext.Snapshot.prototype%"*, « [[AsyncSnapshotMapping]] »). 1. Set _asyncSnapshot_.[[AsyncSnapshotMapping]] to _snapshotMapping_. 1. Return _asyncSnapshot_.
    - -

    Properties of the AsyncSnapshot Prototype Object

    -

    The AsyncSnapshot prototype object:

    + +

    Properties of the AsyncContext.Snapshot Prototype Object

    +

    The AsyncContext.Snapshot prototype object:

      -
    • is %AsyncSnapshot.prototype%.
    • +
    • is %AsyncContext.Snapshot.prototype%.
    • has a [[Prototype]] internal slot whose value is %Object.prototype%.
    • is an ordinary object.
    • -
    • does not have any of the other internal slots of AsyncSnapshot instances.
    • +
    • does not have any of the other internal slots of AsyncContext.Snapshot instances.
    - -

    AsyncSnapshot.prototype.restore ( _func_, ..._args_ )

    + +

    AsyncContext.Snapshot.prototype.restore ( _func_, ..._args_ )

    This method performs the following steps when called:

    1. Let _asyncSnapshot_ be the *this* value. 1. Perform ? RequireInternalSlot(_asyncSnapshot_, [[AsyncSnapshotMapping]]). - 1. Let _snapshotMapping_ be _asyncSnapshot_.[[AsyncSnapshotMapping]]. - 1. Let _previousContextMapping_ be AsyncContextSnapshot(). - 1. AsyncContextSwap(_snapshotMapping_). + 1. Let _previousContextMapping_ be AsyncContextSwap(_asyncSnapshot_.[[AsyncSnapshotMapping]]). 1. Let _result_ be Completion(Call(_func_, *undefined*, _args_)). 1. AsyncContextSwap(_previousContextMapping_). 1. Return _result_. @@ -336,11 +363,11 @@

    AsyncSnapshot.prototype.restore ( _func_, ..._args_ )

    - -

    Properties of AsyncSnapshot Instances

    -

    AsyncSnapshot instances are ordinary objects that inherit properties from the AsyncSnapshot prototype object (the intrinsic, %AsyncSnapshot.prototype%). AsyncSnapshot instances are initially created with the internal slots described in .

    + +

    Properties of AsyncContext.Snapshot Instances

    +

    AsyncContext.Snapshot instances are ordinary objects that inherit properties from the AsyncContext.Snapshot prototype object (the intrinsic, %AsyncContext.Snapshot.prototype%). AsyncContext.Snapshot instances are initially created with the internal slots described in .

    - +
    @@ -361,7 +388,7 @@

    Properties of AsyncSnapshot Instances

    a List of Async Context Mapping Records
    - Represents the snapshotted surrounding agent's Agent Record's [[AsyncContextMapping]] of the AsyncSnapshot instance. + Represents the snapshotted surrounding agent's Agent Record's [[AsyncContextMapping]] of the AsyncContext.Snapshot instance.
    @@ -369,22 +396,22 @@

    Properties of AsyncSnapshot Instances

    - -

    AsyncLocal Objects

    + +

    AsyncContext.Variable Objects

    - -

    The AsyncLocal Constructor

    -

    The AsyncLocal constructor:

    + +

    The AsyncContext.Variable Constructor

    +

    The AsyncContext.Variable constructor:

      -
    • is %AsyncLocal%.
    • -
    • is the initial value of the *"AsyncLocal"* property of the global object.
    • -
    • creates and initializes a new AsyncLocal when called as a constructor.
    • +
    • is %AsyncContext.Variable%.
    • +
    • is the initial value of the *"AsyncContext.Variable"* property of the %AsyncContext% object.
    • +
    • creates and initializes a new AsyncContext.Variable when called as a constructor.
    • is not intended to be called as a function and will throw an exception when called in that manner.
    • -
    • may be used as the value in an `extends` clause of a class definition. Subclass constructors that intend to inherit the specified AsyncLocal behaviour must include a `super` call to the AsyncLocal constructor to create and initialize the subclass instance with the internal state necessary to support the `AsyncLocal.prototype` built-in methods.
    • +
    • may be used as the value in an `extends` clause of a class definition. Subclass constructors that intend to inherit the specified AsyncContext.Variable behaviour must include a `super` call to the AsyncContext.Variable constructor to create and initialize the subclass instance with the internal state necessary to support the `AsyncContext.Variable.prototype` built-in methods.
    - -

    AsyncLocal ( _options_ )

    + +

    AsyncContext.Variable ( _options_ )

    This function performs the following steps when called:

    @@ -395,40 +422,40 @@

    AsyncLocal ( _options_ )

    1. Let _namePresent_ be ? HasProperty(_options_, *"name"*). 1. If _namePresent_ is *true*, then 1. Let _name_ be ? Get(_options_, *"name"*). - 1. Let _nameStr_ be ? ToString(_name_). - 1. Let _defaultValue_ be ? Get(_options_, *"defaultValue"*). - 1. Let _asyncLocal_ be ? OrdinaryCreateFromConstructor(NewTarget, *"%AsyncLocal.prototype%"*, « [[AsyncLocalName]], [[AsyncLocalDefaultValue]] »). - 1. Set _asyncLocal_.[[AsyncLocalName]] to _nameStr_. - 1. Set _asyncLocal_.[[AsyncLocalDefaultValue]] to _defaultValue_. - 1. Return _asyncLocal_. + 1. Set _nameStr_ to ? ToString(_name_). + 1. SEt _defaultValue_ to ? Get(_options_, *"defaultValue"*). + 1. Let _asyncVariable_ be ? OrdinaryCreateFromConstructor(NewTarget, *"%AsyncContext.Variable.prototype%"*, « [[AsyncVariableName]], [[AsyncVariableDefaultValue]] »). + 1. Set _asyncVariable_.[[AsyncVariableName]] to _nameStr_. + 1. Set _asyncVariable_.[[AsyncVariableDefaultValue]] to _defaultValue_. + 1. Return _asyncVariable_.
    - -

    Properties of the AsyncLocal Prototype Object

    -

    The AsyncLocal prototype object:

    + +

    Properties of the AsyncContext.Variable Prototype Object

    +

    The AsyncContext.Variable prototype object:

      -
    • is %AsyncLocal.prototype%.
    • +
    • is %AsyncContext.Variable.prototype%.
    • has a [[Prototype]] internal slot whose value is %Object.prototype%.
    • is an ordinary object.
    • -
    • does not have any of the other internal slots of AsyncLocal instances.
    • +
    • does not have any of the other internal slots of AsyncContext.Variable instances.
    - -

    AsyncLocal.prototype.run ( _value_, _func_, ..._args_ )

    + +

    AsyncContext.Variable.prototype.run ( _value_, _func_, ..._args_ )

    This method performs the following steps when called:

    - 1. Let _asyncLocal_ be the *this* value. - 1. Perform ? RequireInternalSlot(_asyncLocal_, [[AsyncLocalName]]). + 1. Let _asyncVariable_ be the *this* value. + 1. Perform ? RequireInternalSlot(_asyncVariable_, [[AsyncVariableName]]). 1. Let _previousContextMapping_ be AsyncContextSnapshot(). 1. Let _asyncContextMapping_ be a new empty List. 1. For each Async Context Mapping Record _p_ of _previousContextMapping_, do - 1. If SameValueZero(_p_.[[AsyncContextKey]], _asyncLocal_) is *false*, then + 1. If SameValueZero(_p_.[[AsyncContextKey]], _asyncVariable_) is *false*, then 1. Let _q_ be the Async Context Mapping Record { [[AsyncContextKey]]: _p_.[[AsyncContextKey]], [[AsyncContextValue]]: _p_.[[AsyncContextValue]] }. 1. Append _q_ to _asyncContextMapping_. - 1. Assert: _asyncContextMapping_ does not contain an Async Context Mapping Record whose [[AsyncContextKey]] is _asyncLocal_. - 1. Let _p_ be the Async Context Mapping Record { [[AsyncContextKey]]: _asyncLocal_, [[AsyncContextValue]]: _value_ }. + 1. Assert: _asyncContextMapping_ does not contain an Async Context Mapping Record whose [[AsyncContextKey]] is _asyncVariable_. + 1. Let _p_ be the Async Context Mapping Record { [[AsyncContextKey]]: _asyncVariable_, [[AsyncContextValue]]: _value_ }. 1. Append _p_ to _asyncContextMapping_. 1. AsyncContextSwap(_asyncContextMapping_). 1. Let _result_ be Completion(Call(_func_, *undefined*, _args_)). @@ -437,36 +464,36 @@

    AsyncLocal.prototype.run ( _value_, _func_, ..._args_ )

    - -

    get AsyncLocal.prototype.name

    -

    `AsyncLocal.prototype.name` is an accessor property whose set accessor function is *undefined*. Its get accessor function performs the following steps when called:

    + +

    get AsyncContext.Variable.prototype.name

    +

    `AsyncContext.Variable.prototype.name` is an accessor property whose set accessor function is *undefined*. Its get accessor function performs the following steps when called:

    - 1. Let _asyncLocal_ be the *this* value. - 1. Perform ? RequireInternalSlot(_asyncLocal_, [[AsyncLocalName]]). - 1. Return _asyncLocal_.[[AsyncLocalName]]. + 1. Let _asyncVariable_ be the *this* value. + 1. Perform ? RequireInternalSlot(_asyncVariable_, [[AsyncVariableName]]). + 1. Return _asyncVariable_.[[AsyncVariableName]].
    - -

    AsyncLocal.prototype.get ( )

    + +

    AsyncContext.Variable.prototype.get ( )

    This method performs the following steps when called:

    - 1. Let _asyncLocal_ be the *this* value. - 1. Perform ? RequireInternalSlot(_asyncLocal_, [[AsyncLocalName]]). + 1. Let _asyncVariable_ be the *this* value. + 1. Perform ? RequireInternalSlot(_asyncVariable_, [[AsyncVariableDefaultValue]]). 1. Let _agentRecord_ be the surrounding agent's Agent Record. 1. Let _asyncContextMapping_ be _agentRecord_.[[AsyncContextMapping]]. 1. For each Async Context Mapping Record _p_ of _asyncContextMapping_, do - 1. If SameValueZero(_p_.[[AsyncContextKey]], _asyncLocal_) is *true*, return _p_.[[AsyncContextValue]]. - 1. Return _asyncLocal_.[[AsyncLocalDefaultValue]]. + 1. If SameValueZero(_p_.[[AsyncContextKey]], _asyncVariable_) is *true*, return _p_.[[AsyncContextValue]]. + 1. Return _asyncVariable_.[[AsyncVariableDefaultValue]].
    - -

    Properties of AsyncLocal Instances

    -

    AsyncLocal instances are ordinary objects that inherit properties from the AsyncLocal prototype object (the intrinsic, %AsyncLocal.prototype%). AsyncLocal instances are initially created with the internal slots described in .

    + +

    Properties of AsyncContext.Variable Instances

    +

    AsyncContext.Variable instances are ordinary objects that inherit properties from the AsyncContext.Variable prototype object (the intrinsic, %AsyncContext.Variable.prototype%). AsyncContext.Variable instances are initially created with the internal slots described in .

    - +
    @@ -481,24 +508,24 @@

    Properties of AsyncLocal Instances

    - [[AsyncLocalName]] + [[AsyncVariableName]] a String - The name of the AsyncLocal instance. + The name of the AsyncContext.Variable instance.
    - [[AsyncLocalDefaultValue]] + [[AsyncVariableDefaultValue]] an ECMAScript language value - The default value of the AsyncLocal instance when no entry is found in the mapping. + The default value of the AsyncContext.Variable instance when no entry is found in the mapping.
    From 892fc1e85cc86a48ec43909af73776091f04340e Mon Sep 17 00:00:00 2001 From: Chengzhong Wu Date: Tue, 13 Jun 2023 16:45:05 +0800 Subject: [PATCH 06/10] Apply suggestions from code review Co-authored-by: Andreu Botella --- spec.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec.html b/spec.html index 0d330bb..c08b3dc 100644 --- a/spec.html +++ b/spec.html @@ -319,7 +319,7 @@

    The AsyncContext.Snapshot Constructor

    The AsyncContext.Snapshot constructor:

    • is %AsyncContext.Snapshot%.
    • -
    • is the initial value of the *"AsyncContext.Snapshot"* property of the %AsyncContext% object.
    • +
    • is the initial value of the *"Snapshot"* property of the %AsyncContext% object.
    • creates and initializes a new AsyncContext.Snapshot when called as a constructor.
    • is not intended to be called as a function and will throw an exception when called in that manner.
    • may be used as the value in an `extends` clause of a class definition. Subclass constructors that intend to inherit the specified AsyncContext.Snapshot behaviour must include a `super` call to the AsyncContext.Snapshot constructor to create and initialize the subclass instance with the internal state necessary to support the `AsyncContext.Snapshot` and `AsyncContext.Snapshot.prototype` built-in methods.
    • @@ -404,7 +404,7 @@

      The AsyncContext.Variable Constructor

      The AsyncContext.Variable constructor:

      • is %AsyncContext.Variable%.
      • -
      • is the initial value of the *"AsyncContext.Variable"* property of the %AsyncContext% object.
      • +
      • is the initial value of the *"Variable"* property of the %AsyncContext% object.
      • creates and initializes a new AsyncContext.Variable when called as a constructor.
      • is not intended to be called as a function and will throw an exception when called in that manner.
      • may be used as the value in an `extends` clause of a class definition. Subclass constructors that intend to inherit the specified AsyncContext.Variable behaviour must include a `super` call to the AsyncContext.Variable constructor to create and initialize the subclass instance with the internal state necessary to support the `AsyncContext.Variable.prototype` built-in methods.
      • From a71bf4a7338266b3b1cd2be7ec203499ae40b7c9 Mon Sep 17 00:00:00 2001 From: Justin Ridgewell Date: Tue, 13 Jun 2023 12:32:16 -0400 Subject: [PATCH 07/10] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3cef8ad..80142bf 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,7 @@ namespace AsyncContext { get name(): string; - run(value: T, fn: () => R): R; + run(value: T, fn: (...args: any[])=> R, ...args: any[]): R; get(): T | undefined; } From 1eec9c07b32324111ad3c4b86e7e6fbb5d244ec7 Mon Sep 17 00:00:00 2001 From: Justin Ridgewell Date: Tue, 13 Jun 2023 12:32:23 -0400 Subject: [PATCH 08/10] Update spec.html --- spec.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec.html b/spec.html index c08b3dc..8008d9a 100644 --- a/spec.html +++ b/spec.html @@ -423,7 +423,7 @@

        AsyncContext.Variable ( _options_ )

        1. If _namePresent_ is *true*, then 1. Let _name_ be ? Get(_options_, *"name"*). 1. Set _nameStr_ to ? ToString(_name_). - 1. SEt _defaultValue_ to ? Get(_options_, *"defaultValue"*). + 1. Set _defaultValue_ to ? Get(_options_, *"defaultValue"*). 1. Let _asyncVariable_ be ? OrdinaryCreateFromConstructor(NewTarget, *"%AsyncContext.Variable.prototype%"*, « [[AsyncVariableName]], [[AsyncVariableDefaultValue]] »). 1. Set _asyncVariable_.[[AsyncVariableName]] to _nameStr_. 1. Set _asyncVariable_.[[AsyncVariableDefaultValue]] to _defaultValue_. From bf0df785ef3b85d9de4716542f1d548e43422c89 Mon Sep 17 00:00:00 2001 From: legendecas Date: Wed, 14 Jun 2023 00:51:32 +0800 Subject: [PATCH 09/10] fixup! --- README.md | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 80142bf..5712f8b 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,7 @@ Non-goals: # Proposed Solution -`AsyncContext.Variable` are designed as a value store for context propagation across +`AsyncContext` are designed as a value store for context propagation across logically-connected sync/async code execution. ```typescript @@ -178,12 +178,12 @@ namespace AsyncContext { } ``` -`AsyncContext.Variable.prototype.run()` and `AsyncContext.Variable.prototype.get()` sets and gets -the current value of an async execution flow. `AsyncContext.Snapshot` allows you -to opaquely capture the current value of all `AsyncContext.Variable`s and execute a +`Variable.prototype.run()` and `Variable.prototype.get()` sets and gets +the current value of an async execution flow. `Snapshot` allows you +to opaquely capture the current value of all `Variable`s and execute a function at a later time with as if those values were still the current values -(a snapshot and restore). Note that even with `AsyncContext.Snapshot`, you can -only access the value associated with an `AsyncContext.Variable` instance if you have +(a snapshot and restore). Note that even with `Snapshot`, you can +only access the value associated with an `Variable` instance if you have access to that instance. ```typescript @@ -240,7 +240,7 @@ function randomTimeout() { } ``` -`AsyncContext.Snapshot` is useful for implementing APIs that logically "schedule" a +`Snapshot` is useful for implementing APIs that logically "schedule" a callback, so the callback will be called with the context that it logically belongs to, regardless of the context under which it actually runs: @@ -250,9 +250,7 @@ let queue = []; export function enqueueCallback(cb: () => void) { // Each callback is stored with the context at which it was enqueued. const snapshot = new AsyncContext.Snapshot(); - queue.push(() => { - snapshot.restore(cb); - }); + queue.push(() => snapshot.restore(cb)); } runWhenIdle(() => { @@ -266,7 +264,7 @@ runWhenIdle(() => { ``` > Note: There are controversial thought on the dynamic scoping and -> `AsyncContext.Variable`, checkout [SCOPING.md][] for more details. +> `Variable`, checkout [SCOPING.md][] for more details. ## Use cases From 5c980fbbdbb6573af0dad966bcdef67ecaf4edbc Mon Sep 17 00:00:00 2001 From: legendecas Date: Wed, 14 Jun 2023 01:50:13 +0800 Subject: [PATCH 10/10] fixup! revert back to `AsyncContext.Snapshot.prototype.run` --- README.md | 6 +++--- spec.html | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5712f8b..c4b762c 100644 --- a/README.md +++ b/README.md @@ -173,7 +173,7 @@ namespace AsyncContext { class Snapshot { constructor(); - restore(fn: (...args: any[]) => R, ...args: any[]): R; + run(fn: (...args: any[]) => R, ...args: any[]): R; } } ``` @@ -227,7 +227,7 @@ function main() { // The snapshotDuringTop will restore all AsyncContext.Variable to their snapshot // state and invoke the wrapped function. We pass a function which it will // invoke. - snapshotDuringTop.restore(() => { + snapshotDuringTop.run(() => { // Despite being lexically nested inside 'C', the snapshot restored us to // to the 'top' state. console.log(asyncVar.get()); // => 'top' @@ -250,7 +250,7 @@ let queue = []; export function enqueueCallback(cb: () => void) { // Each callback is stored with the context at which it was enqueued. const snapshot = new AsyncContext.Snapshot(); - queue.push(() => snapshot.restore(cb)); + queue.push(() => snapshot.run(cb)); } runWhenIdle(() => { diff --git a/spec.html b/spec.html index 8008d9a..bf72b44 100644 --- a/spec.html +++ b/spec.html @@ -349,8 +349,8 @@

        Properties of the AsyncContext.Snapshot Prototype Object

      • does not have any of the other internal slots of AsyncContext.Snapshot instances.
      - -

      AsyncContext.Snapshot.prototype.restore ( _func_, ..._args_ )

      + +

      AsyncContext.Snapshot.prototype.run ( _func_, ..._args_ )

      This method performs the following steps when called:

      1. Let _asyncSnapshot_ be the *this* value.