Skip to content

Commit 7ba0f2a

Browse files
Try Inverted API
First commit of inverting the API as proposed in tc39#105
1 parent 0f64be9 commit 7ba0f2a

File tree

1 file changed

+72
-72
lines changed

1 file changed

+72
-72
lines changed

README.md

+72-72
Original file line numberDiff line numberDiff line change
@@ -165,74 +165,70 @@ logically-connected sync/async code execution.
165165

166166
```typescript
167167
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;
178170

179171
class Snapshot {
180172
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;
183176
}
184177
}
185178
```
186179
187-
## `AsyncContext.Variable`
180+
## `run` creates scope
188181
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
191184
can be snapshot and restored with `Snapshot`.
192185
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
195190
196191
```typescript
197-
const asyncVar = new AsyncContext.Variable();
192+
const asyncVar = Symbol('asyncVar');
193+
const { get, run } = AsyncContext;
198194

199195
// Sets the current value to 'top', and executes the `main` function.
200-
asyncVar.run("top", main);
196+
run({ [asyncVar]: "top" }, main);
201197

202198
function main() {
203-
// AsyncContext.Variable is maintained through other platform queueing.
199+
// the scope is maintained through other platform queueing.
204200
setTimeout(() => {
205-
console.log(asyncVar.get()); // => 'top'
201+
console.log(get(asyncVar)); // => 'top'
206202

207-
asyncVar.run("A", () => {
208-
console.log(asyncVar.get()); // => 'A'
203+
run({ [asyncVar]: "A" }, () => {
204+
console.log(get(asyncVar)); // => 'A'
209205

210206
setTimeout(() => {
211-
console.log(asyncVar.get()); // => 'A'
207+
console.log(get(asyncVar)); // => 'A'
212208
}, randomTimeout());
213209
});
214210
}, randomTimeout());
215211

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'
219215

220216
setTimeout(() => {
221-
console.log(asyncVar.get()); // => 'B'
217+
console.log(get(asyncVar)); // => 'B'
222218
}, randomTimeout());
223219
});
224220

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'
227223
}
228224

229225
function randomTimeout() {
230226
return Math.random() * 1000;
231227
}
232228
```
233229
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.
236232
237233
Hosts are expected to use the infrastructure in this proposal to allow tracking
238234
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
247243
and execute a function at a later time as if those values were still the
248244
current values (a snapshot and restore).
249245
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
251248
a `Variable` instance if you have access to that instance.
252249
253250
```typescript
254-
const asyncVar = new AsyncContext.Variable();
251+
const asyncVar = Symbol('asyncVar');
252+
const { get, run, SnapShot } = AsyncContext;
255253

256254
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();
260258
});
261259

262-
asyncVar.run("B", () => {
263-
console.log(asyncVar.get()); // => 'B'
260+
run({ [asyncVar]: "B" }, () => {
261+
console.log(get(asyncVar)); // => 'B'
264262

265263
// The snapshot will restore all AsyncContext.Variable to their snapshot
266264
// state and invoke the wrapped function. We pass a function which it will
267265
// invoke.
268266
snapshot.run(() => {
269267
// Despite being lexically nested inside 'B', the snapshot restored us to
270268
// to the snapshot 'A' state.
271-
console.log(asyncVar.get()); // => 'A'
269+
console.log(get(asyncVar)); // => 'A'
272270
});
273271
});
274272
```
@@ -301,20 +299,21 @@ found in [SNAPSHOT.md](./SNAPSHOT.md).
301299
302300
### `AsyncContext.Snapshot.wrap`
303301
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.
307305
308306
```typescript
309-
const asyncVar = new AsyncContext.Variable();
307+
const asyncVar = Symbol('asyncVar');
308+
const { get, run } = AsyncContext;
310309

311310
function fn() {
312-
return asyncVar.get();
311+
return get(asyncVar);
313312
}
314313

315314
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
318317
// wrapped closure that restores that state.
319318
wrappedFn = AsyncContext.Snapshot.wrap(fn)
320319
});
@@ -331,19 +330,20 @@ is executed in the correct execution context.
331330
332331
```typescript
333332
// User code that uses a legacy library
334-
const asyncVar = new AsyncContext.Variable();
333+
const asyncVar = Symbol('asyncVar');
334+
const { get, run } = AsyncContext;
335335

336336
function fn() {
337-
return asyncVar.get();
337+
return get(asyncVar);
338338
}
339339

340-
asyncVar.run("A", () => {
340+
run({ [asyncVar]: "A" }, () => {
341341
defer(fn); // setTimeout schedules during "A" context.
342342
})
343-
asyncVar.run("B", () => {
343+
run({ [asyncVar]: "B" }, () => {
344344
defer(fn); // setTimeout is not called, fn will still see "A" context.
345345
})
346-
asyncVar.run("C", () => {
346+
run({ [asyncVar]: "C" }, () => {
347347
const wrapped = AsyncContext.Snapshot.wrap(fn);
348348
defer(wrapped); // wrapped callback captures "C" context.
349349
})
@@ -371,7 +371,7 @@ function processQueue() {
371371
372372
## Determine the initiator of a task
373373
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
375375
`AsyncContext.Variable` and retrieve the span when they need to determine what started
376376
this chain of interaction.
377377
@@ -381,20 +381,21 @@ tracing span doesn't need to be manually passing around by usercodes.
381381
```typescript
382382
// tracer.js
383383

384-
const asyncVar = new AsyncContext.Variable();
384+
const asyncVar = Symbol('asyncVar');
385+
385386
export function run(cb) {
386387
// (a)
387388
const span = {
388389
startTime: Date.now(),
389390
traceId: randomUUID(),
390391
spanId: randomUUID(),
391392
};
392-
asyncVar.run(span, cb);
393+
AsyncContext.run({ [asyncVar]: span }, cb);
393394
}
394395

395396
export function end() {
396397
// (b)
397-
const span = asyncVar.get();
398+
const span = AsyncContext.get(asyncVar);
398399
span?.endTime = Date.now();
399400
}
400401
```
@@ -430,20 +431,19 @@ concurrent multi-tracking.
430431
431432
## Transitive task attribution
432433
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.
436436
437437
```typescript
438438
const scheduler = {
439-
asyncVar: new AsyncContext.Variable(),
439+
asyncVar: Symbol('asyncVar'),
440440
postTask(task, options) {
441441
// In practice, the task execution may be deferred.
442442
// 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);
444444
},
445445
currentTask() {
446-
return this.asyncVar.get() ?? { priority: "default" };
446+
return AsyncContext.get(asyncVar) ?? { priority: "default" };
447447
},
448448
};
449449

@@ -468,9 +468,9 @@ async function doStuffs(text) {
468468
## User-land queues
469469
470470
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
472472
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.
474474
475475
```typescript
476476
// The scheduler doesn't access to any AsyncContext.Variable.
@@ -493,14 +493,14 @@ const scheduler = {
493493

494494
function userAction() {
495495
scheduler.postTask(function userTask() {
496-
console.log(traceContext.get());
496+
console.log(AsyncContext.get(traceContext));
497497
});
498498
}
499499

500500
// 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);
504504

505505
scheduler.runWhenIdle();
506506
// 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
519519
their parent or sibling scopes.
520520
521521
```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'
525525

526526
// ...arbitrary synchronous codes.
527527
// ...or await-ed asynchronous calls.
528528

529529
// The value can not be modified at this point.
530-
asyncVar.get(); // => 'A'
530+
AsyncContext.get(asyncVar); // => 'A'
531531
});
532532
```
533533

0 commit comments

Comments
 (0)