@@ -154,81 +154,83 @@ Non-goals:
154
154
logically-connected sync/async code execution.
155
155
156
156
``` typescript
157
- class AsyncContext <T > {
158
- static wrap<R >(callback : (... args : any []) => R ): (... args : any []) => R ;
157
+ namespace AsyncContext {
158
+ class Variable <T > {
159
+ constructor (options : AsyncVariableOptions <T >);
159
160
160
- constructor ( options : AsyncContextOptions < T >) ;
161
+ get name() : string ;
161
162
162
- get name() : string ;
163
+ run< R >( value : T , fn : ( ... args : any []) => R , ... args : any []) : R ;
163
164
164
- run<R >(value : T , callback : () => R ): R ;
165
+ get(): T | undefined ;
166
+ }
165
167
166
- get(): T | undefined ;
167
- }
168
+ interface AsyncVariableOptions <T > {
169
+ name? : string ;
170
+ defaultValue? : T ;
171
+ }
172
+
173
+ class Snapshot {
174
+ constructor ();
168
175
169
- interface AsyncContextOptions <T > {
170
- name? : string ;
171
- defaultValue? : T ;
176
+ run<R >(fn : (... args : any []) => R , ... args : any []): R ;
177
+ }
172
178
}
173
179
```
174
180
175
- ` AsyncContext .prototype.run()` and ` AsyncContext .prototype.get()` sets and gets
176
- the current value of an async execution flow. ` AsyncContext.wrap() ` allows you
177
- to opaquely capture the current value of all ` AsyncContext ` s and execute the
178
- callback at a later time with as if those values were still the current values
179
- (a snapshot and restore). Note that even with ` AsyncContext.wrap() ` , you can
180
- only access the value associated with an ` AsyncContext ` instance if you have
181
+ ` Variable .prototype.run()` and ` Variable .prototype.get()` sets and gets
182
+ the current value of an async execution flow. ` Snapshot ` allows you
183
+ to opaquely capture the current value of all ` Variable ` s and execute a
184
+ function at a later time with as if those values were still the current values
185
+ (a snapshot and restore). Note that even with ` Snapshot ` , you can
186
+ only access the value associated with an ` Variable ` instance if you have
181
187
access to that instance.
182
188
183
189
``` typescript
184
- const context = new AsyncContext ();
190
+ const asyncVar = new AsyncContext . Variable ();
185
191
186
192
// Sets the current value to 'top', and executes the `main` function.
187
- context .run (" top" , main );
193
+ asyncVar .run (" top" , main );
188
194
189
195
function main() {
190
- // Context is maintained through other platform queueing.
196
+ // AsyncContext.Variable is maintained through other platform queueing.
191
197
setTimeout (() => {
192
- console .log (context .get ()); // => 'top'
198
+ console .log (asyncVar .get ()); // => 'top'
193
199
194
- context .run (" A" , () => {
195
- console .log (context .get ()); // => 'A'
200
+ asyncVar .run (" A" , () => {
201
+ console .log (asyncVar .get ()); // => 'A'
196
202
197
203
setTimeout (() => {
198
- console .log (context .get ()); // => 'A'
204
+ console .log (asyncVar .get ()); // => 'A'
199
205
}, randomTimeout ());
200
206
});
201
207
}, randomTimeout ());
202
208
203
- // Context runs can be nested.
204
- context .run (" B" , () => {
205
- console .log (context .get ()); // => 'B'
209
+ // AsyncContext.Variable runs can be nested.
210
+ asyncVar .run (" B" , () => {
211
+ console .log (asyncVar .get ()); // => 'B'
206
212
207
213
setTimeout (() => {
208
- console .log (context .get ()); // => 'B'
214
+ console .log (asyncVar .get ()); // => 'B'
209
215
}, randomTimeout ());
210
216
});
211
217
212
- // Context was restored after the previous run.
213
- console .log (context .get ()); // => 'top'
218
+ // AsyncContext.Variable was restored after the previous run.
219
+ console .log (asyncVar .get ()); // => 'top'
214
220
215
- // Captures the state of all AsyncContext's at this moment.
216
- const snapshotDuringTop = AsyncContext .wrap ((cb ) => {
217
- console .log (context .get ()); // => 'top'
218
- cb ();
219
- });
221
+ // Captures the state of all AsyncContext.Variable's at this moment.
222
+ const snapshotDuringTop = new AsyncContext .Snapshot ();
220
223
221
- // Context runs can be nested.
222
- context .run (" C" , () => {
223
- console .log (context .get ()); // => 'C'
224
+ asyncVar .run (" C" , () => {
225
+ console .log (asyncVar .get ()); // => 'C'
224
226
225
- // The snapshotDuringTop will restore all AsyncContext to their snapshot
226
- // state and invoke the wrapped function. We pass a callback which it will
227
+ // The snapshotDuringTop will restore all AsyncContext.Variable to their snapshot
228
+ // state and invoke the wrapped function. We pass a function which it will
227
229
// invoke.
228
- snapshotDuringTop (() => {
230
+ snapshotDuringTop . run (() => {
229
231
// Despite being lexically nested inside 'C', the snapshot restored us to
230
232
// to the 'top' state.
231
- console .log (context .get ()); // => 'top'
233
+ console .log (asyncVar .get ()); // => 'top'
232
234
});
233
235
});
234
236
}
@@ -238,7 +240,7 @@ function randomTimeout() {
238
240
}
239
241
```
240
242
241
- ` AsyncContext.wrap ` is useful for implementing APIs that logically "schedule" a
243
+ ` Snapshot ` is useful for implementing APIs that logically "schedule" a
242
244
callback, so the callback will be called with the context that it logically
243
245
belongs to, regardless of the context under which it actually runs:
244
246
@@ -247,7 +249,8 @@ let queue = [];
247
249
248
250
export function enqueueCallback(cb : () => void ) {
249
251
// Each callback is stored with the context at which it was enqueued.
250
- queue .push (AsyncContext .wrap (cb ));
252
+ const snapshot = new AsyncContext .Snapshot ();
253
+ queue .push (() => snapshot .run (cb ));
251
254
}
252
255
253
256
runWhenIdle (() => {
@@ -261,11 +264,11 @@ runWhenIdle(() => {
261
264
```
262
265
263
266
> Note: There are controversial thought on the dynamic scoping and
264
- > ` AsyncContext ` , checkout [ SCOPING.md] [ ] for more details.
267
+ > ` Variable ` , checkout [ SCOPING.md] [ ] for more details.
265
268
266
269
## Use cases
267
270
268
- Use cases for ` AsyncContext ` include:
271
+ Use cases for async context include:
269
272
270
273
- Annotating logs with information related to an asynchronous callstack.
271
274
@@ -300,7 +303,7 @@ A detailed example usecase can be found [here](./USE-CASES.md)
300
303
## Determine the initiator of a task
301
304
302
305
Application monitoring tools like OpenTelemetry save their tracing spans in the
303
- ` AsyncContext ` and retrieve the span when they need to determine what started
306
+ ` AsyncContext.Variable ` and retrieve the span when they need to determine what started
304
307
this chain of interaction.
305
308
306
309
These libraries can not intrude the developer APIs for seamless monitoring. The
@@ -309,20 +312,20 @@ tracing span doesn't need to be manually passing around by usercodes.
309
312
``` typescript
310
313
// tracer.js
311
314
312
- const context = new AsyncContext ();
315
+ const asyncVar = new AsyncContext . Variable ();
313
316
export function run(cb ) {
314
317
// (a)
315
318
const span = {
316
319
startTime: Date .now (),
317
320
traceId: randomUUID (),
318
321
spanId: randomUUID (),
319
322
};
320
- context .run (span , cb );
323
+ asyncVar .run (span , cb );
321
324
}
322
325
323
326
export function end() {
324
327
// (b)
325
- const span = context .get ();
328
+ const span = asyncVar .get ();
326
329
span ?.endTime = Date .now ();
327
330
}
328
331
```
@@ -358,20 +361,20 @@ concurrent multi-tracking.
358
361
359
362
## Transitive task attribution
360
363
361
- User tasks can be scheduled with attributions. With ` AsyncContext ` , task
364
+ User tasks can be scheduled with attributions. With ` AsyncContext.Variable ` , task
362
365
attributions are propagated in the async task flow and sub-tasks can be
363
366
scheduled with the same priority.
364
367
365
368
``` typescript
366
369
const scheduler = {
367
- context : new AsyncContext (),
370
+ asyncVar : new AsyncContext . Variable (),
368
371
postTask(task , options ) {
369
372
// In practice, the task execution may be deferred.
370
- // Here we simply run the task immediately with the context .
371
- return this .context .run ({ priority: options .priority }, task );
373
+ // Here we simply run the task immediately.
374
+ return this .asyncVar .run ({ priority: options .priority }, task );
372
375
},
373
376
currentTask() {
374
- return this .context .get () ?? { priority: " default" };
377
+ return this .asyncVar .get () ?? { priority: " default" };
375
378
},
376
379
};
377
380
0 commit comments