@@ -184,13 +184,14 @@ namespace AsyncContext {
184
184
}
185
185
```
186
186
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
+
187
193
` 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.
194
195
195
196
``` typescript
196
197
const asyncVar = new AsyncContext .Variable ();
@@ -223,29 +224,55 @@ function main() {
223
224
224
225
// AsyncContext.Variable was restored after the previous run.
225
226
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
- });
242
227
}
243
228
244
229
function randomTimeout() {
245
230
return Math .random () * 1000 ;
246
231
}
247
232
```
248
233
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
+
249
276
` Snapshot ` is useful for implementing APIs that logically "schedule" a
250
277
callback, so the callback will be called with the context that it logically
251
278
belongs to, regardless of the context under which it actually runs:
@@ -269,16 +296,6 @@ runWhenIdle(() => {
269
296
});
270
297
```
271
298
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
-
282
299
# Examples
283
300
284
301
## Determine the initiator of a task
@@ -377,6 +394,75 @@ async function doStuffs(text) {
377
394
}
378
395
```
379
396
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
+
380
466
# Prior Arts
381
467
382
468
## zones.js
0 commit comments