@@ -165,74 +165,70 @@ logically-connected sync/async code execution.
165
165
166
166
``` typescript
167
167
namespace AsyncContext {
168
- class Variable <T > {
169
- constructor (options : AsyncVariableOptions <T >);
170
- get name(): string ;
171
- get(): T | undefined ;
172
- run<R >(value : T , fn : (... args : any [])=> R , ... args : any []): R ;
173
- }
174
- interface AsyncVariableOptions <T > {
175
- name? : string ;
176
- defaultValue? : T ;
177
- }
168
+ run <R , A extends any []>(scope : { [key : string | Symbol ]: any }, fn : (... args : A )=> R , ... args : A ): R ;
169
+ get (key : string | Symbol ): any ;
178
170
179
171
class Snapshot {
180
172
constructor ();
181
- run<R >(fn : (... args : any []) => R , ... args : any []): R ;
182
- static wrap<T , R >(fn : (this : T , ... args : any []) => R ): (this : T , ... args : any []) => R ;
173
+ run<R , A extends any []>(fn : (... args : A ) => R , ... args : A ): R ;
174
+ get(key : string | Symbol ): any ;
175
+ static wrap<T , R , A extends any []>(fn : (this : T , ... args : A ) => R ): (this : T , ... args : A ) => R ;
183
176
}
184
177
}
185
178
` ` `
186
179
187
- ## ` AsyncContext.Variable `
180
+ ## ` run ` creates scope
188
181
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
182
+ With ` run ` it is possible to specify values that are associated with the current
183
+ execution flow. The values are propagated through async execution flows, and
191
184
can be snapshot and restored with ` Snapshot ` .
192
185
193
- ` Variable.prototype.run() ` and ` Variable.prototype.get() ` sets and gets
194
- the current value of an async execution flow.
186
+ Using ` get ` it is possible to get the current value of a specific field in the execution
187
+ flow.
188
+
189
+ You can pass in any
195
190
196
191
` ` ` typescript
197
- const asyncVar = new AsyncContext .Variable ();
192
+ const asyncVar = Symbol (' asyncVar' );
193
+ const { get, run } = AsyncContext ;
198
194
199
195
// Sets the current value to 'top', and executes the `main` function.
200
- asyncVar . run (" top" , main );
196
+ run ({ [ asyncVar ] : " top" } , main );
201
197
202
198
function main () {
203
- // AsyncContext.Variable is maintained through other platform queueing.
199
+ // the scope is maintained through other platform queueing.
204
200
setTimeout(() => {
205
- console .log (asyncVar . get ()); // => 'top'
201
+ console .log (get (asyncVar )); // => 'top'
206
202
207
- asyncVar . run (" A" , () => {
208
- console .log (asyncVar . get ()); // => 'A'
203
+ run({ [ asyncVar ]: " A" } , () => {
204
+ console .log (get (asyncVar )); // => 'A'
209
205
210
206
setTimeout(() => {
211
- console .log (asyncVar . get ()); // => 'A'
207
+ console .log (get (asyncVar )); // => 'A'
212
208
}, randomTimeout());
213
209
});
214
210
}, randomTimeout());
215
211
216
- // AsyncContext.Variable runs can be nested.
217
- asyncVar . run (" B" , () => {
218
- console .log (asyncVar . get ()); // => 'B'
212
+ // runs can be nested.
213
+ run({ [ asyncVar ]: " B" } , () => {
214
+ console .log (get (asyncVar )); // => 'B'
219
215
220
216
setTimeout(() => {
221
- console .log (asyncVar . get ()); // => 'B'
217
+ console .log (get (asyncVar )); // => 'B'
222
218
}, randomTimeout());
223
219
});
224
220
225
- // AsyncContext.Variable was restored after the previous run.
226
- console .log (asyncVar . get ()); // => 'top'
221
+ // The context was restored after the previous run.
222
+ console .log (get (asyncVar )); // => 'top'
227
223
}
228
224
229
225
function randomTimeout () {
230
226
return Math .random () * 1000 ;
231
227
}
232
228
` ` `
233
229
234
- > Note: There are controversial thought on the dynamic scoping and
235
- > ` Variable ` , checkout [ SCOPING.md] [ ] for more details.
230
+ > Note: There are controversial thoughts on the dynamic scoping,
231
+ > checkout [SCOPING.md][] for more details.
236
232
237
233
Hosts are expected to use the infrastructure in this proposal to allow tracking
238
234
not only asynchronous callstacks, but other ways to schedule jobs on the event
@@ -247,28 +243,30 @@ A detailed example of use cases can be found in the
247
243
and execute a function at a later time as if those values were still the
248
244
current values (a snapshot and restore).
249
245
250
- Note that even with ` Snapshot ` , you can only access the value associated with
246
+ Note that even with ` Snapshot ` , as long as you use keys it remains only possible to access values associated with the
247
+ variable using you can only access the value associated with
251
248
a ` Variable ` instance if you have access to that instance.
252
249
253
250
` ` ` typescript
254
- const asyncVar = new AsyncContext .Variable ();
251
+ const asyncVar = Symbol (' asyncVar' );
252
+ const { get, run, SnapShot } = AsyncContext ;
255
253
256
254
let snapshot
257
- asyncVar . run (" A" , () => {
258
- // Captures the state of all AsyncContext.Variable's at this moment.
259
- snapshot = new AsyncContext . Snapshot ();
255
+ run ({ [ asyncVar ] : " A" } , () => {
256
+ // Captures the state of the entire context scope at this moment.
257
+ snapshot = new Snapshot ();
260
258
});
261
259
262
- asyncVar . run (" B" , () => {
263
- console .log (asyncVar . get ()); // => 'B'
260
+ run ({ [ asyncVar ] : " B" } , () => {
261
+ console .log (get (asyncVar )); // => 'B'
264
262
265
263
// The snapshot will restore all AsyncContext.Variable to their snapshot
266
264
// state and invoke the wrapped function. We pass a function which it will
267
265
// invoke.
268
266
snapshot .run (() => {
269
267
// Despite being lexically nested inside 'B', the snapshot restored us to
270
268
// to the snapshot 'A' state.
271
- console .log (asyncVar . get ()); // => 'A'
269
+ console .log (get (asyncVar )); // => 'A'
272
270
});
273
271
});
274
272
` ` `
@@ -301,20 +299,21 @@ found in [SNAPSHOT.md](./SNAPSHOT.md).
301
299
302
300
### ` AsyncContext .Snapshot .wrap `
303
301
304
- ` AsyncContext.Snapshot.wrap ` is a helper which captures the current values of all
305
- ` Variable ` s and returns a wrapped function. When invoked, this wrapped function
306
- restores the state of all ` Variable ` s and executes the inner function.
302
+ ` AsyncContext .Snapshot .wrap ` is a helper which captures the current scope values
303
+ and returns a wrapped function. When invoked, this wrapped function restores
304
+ the entire state and executes the inner function.
307
305
308
306
` ` ` typescript
309
- const asyncVar = new AsyncContext .Variable ();
307
+ const asyncVar = Symbol (' asyncVar' );
308
+ const { get, run } = AsyncContext ;
310
309
311
310
function fn () {
312
- return asyncVar . get ();
311
+ return get(asyncVar );
313
312
}
314
313
315
314
let wrappedFn ;
316
- asyncVar . run (" A" , () => {
317
- // Captures the state of all AsyncContext.Variable's at this moment, returning
315
+ run ({ [ asyncVar ] : " A" } , () => {
316
+ // Captures the state at this moment, returning
318
317
// wrapped closure that restores that state.
319
318
wrappedFn = AsyncContext .Snapshot .wrap (fn )
320
319
});
@@ -331,19 +330,20 @@ is executed in the correct execution context.
331
330
332
331
` ` ` typescript
333
332
// User code that uses a legacy library
334
- const asyncVar = new AsyncContext .Variable ();
333
+ const asyncVar = Symbol (' asyncVar' );
334
+ const { get, run } = AsyncContext ;
335
335
336
336
function fn () {
337
- return asyncVar . get ();
337
+ return get(asyncVar );
338
338
}
339
339
340
- asyncVar . run (" A" , () => {
340
+ run ({ [ asyncVar ] : " A" } , () => {
341
341
defer(fn ); // setTimeout schedules during "A" context.
342
342
})
343
- asyncVar . run (" B" , () => {
343
+ run ({ [ asyncVar ] : " B" } , () => {
344
344
defer(fn ); // setTimeout is not called, fn will still see "A" context.
345
345
})
346
- asyncVar . run (" C" , () => {
346
+ run ({ [ asyncVar ] : " C" } , () => {
347
347
const wrapped = AsyncContext .Snapshot .wrap (fn );
348
348
defer(wrapped ); // wrapped callback captures "C" context.
349
349
})
@@ -371,7 +371,7 @@ function processQueue() {
371
371
372
372
## Determine the initiator of a task
373
373
374
- Application monitoring tools like OpenTelemetry save their tracing spans in the
374
+ Application monitoring tools like OpenTelemetry save their tracing spans in their the
375
375
` AsyncContext .Variable ` and retrieve the span when they need to determine what started
376
376
this chain of interaction.
377
377
@@ -381,20 +381,21 @@ tracing span doesn't need to be manually passing around by usercodes.
381
381
` ` ` typescript
382
382
// tracer.js
383
383
384
- const asyncVar = new AsyncContext .Variable ();
384
+ const asyncVar = Symbol (' asyncVar' );
385
+
385
386
export function run (cb ) {
386
387
// (a)
387
388
const span = {
388
389
startTime: Date .now (),
389
390
traceId: randomUUID (),
390
391
spanId: randomUUID (),
391
392
};
392
- asyncVar .run (span , cb );
393
+ AsyncContext .run ({ [ asyncVar ] : span } , cb );
393
394
}
394
395
395
396
export function end () {
396
397
// (b)
397
- const span = asyncVar .get ();
398
+ const span = AsyncContext .get (asyncVar );
398
399
span ?.endTime = Date .now ();
399
400
}
400
401
` ` `
@@ -430,20 +431,19 @@ concurrent multi-tracking.
430
431
431
432
## Transitive task attribution
432
433
433
- User tasks can be scheduled with attributions. With ` AsyncContext.Variable ` , task
434
- attributions are propagated in the async task flow and sub-tasks can be
435
- scheduled with the same priority.
434
+ User tasks can be scheduled with attributions. Task attributions are propagated in
435
+ the async task flow and sub-tasks can be scheduled with the same priority.
436
436
437
437
` ` ` typescript
438
438
const scheduler = {
439
- asyncVar: new AsyncContext . Variable ( ),
439
+ asyncVar: Symbol ( ' asyncVar ' ),
440
440
postTask(task , options ) {
441
441
// In practice, the task execution may be deferred.
442
442
// Here we simply run the task immediately.
443
- return this . asyncVar . run ({ priority: options .priority }, task );
443
+ return AsyncContext . run ({ [ asyncVar ]: { priority: options .priority } }, task );
444
444
},
445
445
currentTask() {
446
- return this . asyncVar . get () ?? { priority: " default" };
446
+ return AsyncContext . get (asyncVar ) ?? { priority: " default" };
447
447
},
448
448
};
449
449
@@ -468,9 +468,9 @@ async function doStuffs(text) {
468
468
## User-land queues
469
469
470
470
User-land queues can be implemented with ` AsyncContext .Snapshot ` to propagate
471
- the values of all ` AsyncContext.Variable ` s without access to any of them. This
471
+ all values of the scope without access to any of them. This
472
472
allows the user-land queue to be implemented in a way that is decoupled from
473
- consumers of ` AsyncContext.Variable ` .
473
+ consumers of the values .
474
474
475
475
` ` ` typescript
476
476
// The scheduler doesn't access to any AsyncContext.Variable.
@@ -493,14 +493,14 @@ const scheduler = {
493
493
494
494
function userAction () {
495
495
scheduler .postTask (function userTask () {
496
- console .log (traceContext .get ());
496
+ console .log (AsyncContext .get (traceContext ));
497
497
});
498
498
}
499
499
500
500
// Tracing libraries can use AsyncContext.Variable to store tracing contexts.
501
- const traceContext = new AsyncContext . Variable ( );
502
- traceContext .run (" trace-id-a" , userAction );
503
- traceContext .run (" trace-id-b" , userAction );
501
+ const traceContext = Symbol ( ' traceContext ' );
502
+ AsyncContext .run ({ [ traceContext ] : " trace-id-a" } , userAction );
503
+ AsyncContext .run ({ [ traceContext ] : " trace-id-b" } , userAction );
504
504
505
505
scheduler .runWhenIdle ();
506
506
// The userTask will be run with the trace context it was enqueued with.
@@ -519,15 +519,15 @@ must be taken in a sub-graph of an async execution flow, and can not affect
519
519
their parent or sibling scopes.
520
520
521
521
` ` ` typescript
522
- const asyncVar = new AsyncContext . Variable ( );
523
- asyncVar .run (" A" , async () => {
524
- asyncVar .get (); // => 'A'
522
+ const asyncVar = new Symbol ( ' asyncVar ' );
523
+ AsyncContext .run ({ [ asyncVar ] : " A" } , async () => {
524
+ AsyncContext .get (asyncVar ); // => 'A'
525
525
526
526
// ...arbitrary synchronous codes.
527
527
// ...or await-ed asynchronous calls.
528
528
529
529
// The value can not be modified at this point.
530
- asyncVar .get (); // => 'A'
530
+ AsyncContext .get (asyncVar ); // => 'A'
531
531
});
532
532
` ` `
533
533
0 commit comments