@@ -184,13 +184,14 @@ namespace AsyncContext {
184184}
185185```
186186
187+ ## ` AsyncContext.Variable `
188+
189+ ` Variable ` is a container for a value that is associated with the current
190+ execution flow. The value is propagated through async execution flows, and
191+ can be snapshot and restored with ` Snapshot ` .
192+
187193` Variable.prototype.run() ` and ` Variable.prototype.get() ` sets and gets
188- the current value of an async execution flow. ` Snapshot ` allows you
189- to opaquely capture the current value of all ` Variable ` s and execute a
190- function at a later time with as if those values were still the current values
191- (a snapshot and restore). Note that even with ` Snapshot ` , you can
192- only access the value associated with an ` Variable ` instance if you have
193- access to that instance.
194+ the current value of an async execution flow.
194195
195196``` typescript
196197const asyncVar = new AsyncContext .Variable ();
@@ -223,29 +224,55 @@ function main() {
223224
224225 // AsyncContext.Variable was restored after the previous run.
225226 console .log (asyncVar .get ()); // => 'top'
226-
227- // Captures the state of all AsyncContext.Variable's at this moment.
228- const snapshotDuringTop = new AsyncContext .Snapshot ();
229-
230- asyncVar .run (" C" , () => {
231- console .log (asyncVar .get ()); // => 'C'
232-
233- // The snapshotDuringTop will restore all AsyncContext.Variable to their snapshot
234- // state and invoke the wrapped function. We pass a function which it will
235- // invoke.
236- snapshotDuringTop .run (() => {
237- // Despite being lexically nested inside 'C', the snapshot restored us to
238- // to the 'top' state.
239- console .log (asyncVar .get ()); // => 'top'
240- });
241- });
242227}
243228
244229function randomTimeout() {
245230 return Math .random () * 1000 ;
246231}
247232```
248233
234+ > Note: There are controversial thought on the dynamic scoping and
235+ > ` Variable ` , checkout [ SCOPING.md] [ ] for more details.
236+
237+ Hosts are expected to use the infrastructure in this proposal to allow tracking
238+ not only asynchronous callstacks, but other ways to schedule jobs on the event
239+ loop (such as ` setTimeout ` ) to maximize the value of these use cases.
240+
241+ A detailed example of use cases can be found in the
242+ [ Use cases document] ( ./USE-CASES.md ) .
243+
244+ ## ` AsyncContext.Snapshot `
245+
246+ ` Snapshot ` allows you to opaquely capture the current values of all ` Variable ` s
247+ and execute a function at a later time as if those values were still the
248+ current values (a snapshot and restore).
249+
250+ Note that even with ` Snapshot ` , you can only access the value associated with
251+ a ` Variable ` instance if you have access to that instance.
252+
253+ ``` typescript
254+ const asyncVar = new AsyncContext .Variable ();
255+
256+ let snapshot
257+ asyncVar .run (" A" , () => {
258+ // Captures the state of all AsyncContext.Variable's at this moment.
259+ snapshot = new AsyncContext .Snapshot ();
260+ });
261+
262+ asyncVar .run (" B" , () => {
263+ console .log (asyncVar .get ()); // => 'B'
264+
265+ // The snapshot will restore all AsyncContext.Variable to their snapshot
266+ // state and invoke the wrapped function. We pass a function which it will
267+ // invoke.
268+ snapshot .run (() => {
269+ // Despite being lexically nested inside 'B', the snapshot restored us to
270+ // to the snapshot 'A' state.
271+ console .log (asyncVar .get ()); // => 'A'
272+ });
273+ });
274+ ```
275+
249276` Snapshot ` is useful for implementing APIs that logically "schedule" a
250277callback, so the callback will be called with the context that it logically
251278belongs to, regardless of the context under which it actually runs:
@@ -269,16 +296,6 @@ runWhenIdle(() => {
269296});
270297```
271298
272- > Note: There are controversial thought on the dynamic scoping and
273- > ` Variable ` , checkout [ SCOPING.md] [ ] for more details.
274-
275- Hosts are expected to use the infrastructure in this proposal to allow tracking
276- not only asynchronous callstacks, but other ways to schedule jobs on the event
277- loop (such as ` setTimeout ` ) to maximize the value of these use cases.
278-
279- A detailed example of use cases can be found in the
280- [ Use cases document] ( ./USE-CASES.md ) .
281-
282299# Examples
283300
284301## Determine the initiator of a task
@@ -377,6 +394,75 @@ async function doStuffs(text) {
377394}
378395```
379396
397+ ## User-land queues
398+
399+ User-land queues can be implemented with ` AsyncContext.Snapshot ` to propagate
400+ the values of all ` AsyncContext.Variable ` s without access to any of them. This
401+ allows the user-land queue to be implemented in a way that is decoupled from
402+ consumers of ` AsyncContext.Variable ` .
403+
404+ ``` typescript
405+ // The scheduler doesn't access to any AsyncContext.Variable.
406+ const scheduler = {
407+ queue: [],
408+ postTask(task ) {
409+ // Each callback is stored with the context at which it was enqueued.
410+ const snapshot = new AsyncContext .Snapshot ();
411+ queue .push (() => snapshot .run (task ));
412+ },
413+ runWhenIdle() {
414+ // All callbacks in the queue would be run with the current context if they
415+ // hadn't been wrapped.
416+ for (const cb of this .queue ) {
417+ cb ();
418+ }
419+ this .queue = [];
420+ }
421+ };
422+
423+ function userAction() {
424+ scheduler .postTask (function userTask() {
425+ console .log (traceContext .get ());
426+ });
427+ }
428+
429+ // Tracing libraries can use AsyncContext.Variable to store tracing contexts.
430+ const traceContext = new AsyncContext .Variable ();
431+ traceContext .run (" trace-id-a" , userAction );
432+ traceContext .run (" trace-id-b" , userAction );
433+
434+ runWhenIdle ();
435+ // The userTask will be run with the trace context it was enqueued with.
436+ // => 'trace-id-a'
437+ // => 'trace-id-b'
438+ ```
439+
440+ # FAQ
441+
442+ ## Why take a function in ` run ` ?
443+
444+ The ` Variable.prototype.run ` and ` Snapshot.prototype.run ` methods take a
445+ function to execute because it ensures async context variables
446+ will always contain consistent values in a given execution flow. Any modification
447+ must be taken in a sub-graph of an async execution flow, and can not affect
448+ their parent or sibling scopes.
449+
450+ ``` typescript
451+ const asyncVar = new AsyncContext .Variable ();
452+ asyncVar .run (" A" , async () => {
453+ asyncVar .get (); // => 'A'
454+
455+ // ...arbitrary synchronous codes.
456+ // ...or await-ed asynchronous calls.
457+
458+ // The value can not be modified at this point.
459+ asyncVar .get (); // => 'A'
460+ });
461+ ```
462+
463+ This increases the integrity of async context variables, and makes them
464+ easier to reason about where a value of an async variable comes from.
465+
380466# Prior Arts
381467
382468## zones.js
0 commit comments