Skip to content

Commit 427feca

Browse files
authored
Expand on requirements of Snapshot (tc39#64)
1 parent b2664c5 commit 427feca

File tree

2 files changed

+99
-1
lines changed

2 files changed

+99
-1
lines changed

README.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,9 @@ runWhenIdle(() => {
296296
});
297297
```
298298

299+
A detailed explanation of why `AsyncContext.Snapshot` is a requirement can be
300+
found in [SNAPSHOT.md](./SNAPSHOT.md).
301+
299302
# Examples
300303

301304
## Determine the initiator of a task
@@ -431,7 +434,7 @@ const traceContext = new AsyncContext.Variable();
431434
traceContext.run("trace-id-a", userAction);
432435
traceContext.run("trace-id-b", userAction);
433436

434-
runWhenIdle();
437+
scheduler.runWhenIdle();
435438
// The userTask will be run with the trace context it was enqueued with.
436439
// => 'trace-id-a'
437440
// => 'trace-id-b'

SNAPSHOT.md

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Requirements of `AsyncContext.Snapshot`
2+
3+
`AsyncContext.Snapshot` presents two unique requirements:
4+
5+
- It does not expose the value associated with any `Variable` instances.
6+
- It captures _all_ `Variable`s' current value and restores those values
7+
at a later time.
8+
9+
The above requirements are essential to decouple a queueing implementation
10+
from the consumers of `Variable` instances. For example, a scheduler can queue
11+
an async task and take a snapshot of the current context:
12+
13+
```typescript
14+
// The scheduler doesn't access any AsyncContext.Variable.
15+
const scheduler = {
16+
queue: [],
17+
postTask(task) {
18+
// Each callback is stored with the context at which it was enqueued.
19+
const snapshot = new AsyncContext.Snapshot();
20+
queue.push({ snapshot, task });
21+
},
22+
runWhenIdle() {
23+
const queue = this.queue;
24+
this.queue = [];
25+
for (const { snapshot, task } of queue) {
26+
// All tasks in the queue would be run with the current context if they
27+
// hadn't been wrapped with the snapshot.
28+
snapshot.run(task);
29+
}
30+
}
31+
};
32+
```
33+
34+
In this example, the scheduler can propagate values of `Variable`s but doesn't
35+
have access to any `Variable` instance. They are not coupled with a specific
36+
consumer of `Variable`. A consumer of `Variable` will not be coupled with a
37+
specific scheduler as well.
38+
39+
A consumer like a tracer can use `Variable` without knowing how the scheduler
40+
is implemented:
41+
42+
```typescript
43+
// tracer.js
44+
const asyncVar = new AsyncContext.Variable();
45+
export function run(cb) {
46+
// Create a new span and run the callback with it.
47+
const span = {
48+
startTime: Date.now(),
49+
traceId: randomUUID(),
50+
spanId: randomUUID(),
51+
};
52+
asyncVar.run(span, cb);
53+
}
54+
55+
export function end() {
56+
// Get the current span from the AsyncContext.Variable and end it.
57+
const span = asyncVar.get();
58+
span?.endTime = Date.now();
59+
}
60+
```
61+
62+
The `Snapshot` API enables user-land queueing implementations to be cooperate
63+
with any consumers of `Variable`. For instances, a queueing implementation can
64+
be:
65+
66+
- A user-land Promise-like implementation,
67+
- A user multiplexer that multiplexes an IO operation with a batch of async
68+
tasks.
69+
70+
Without an API like `Snapshot`, a queueing implementation would have to be built
71+
on top of the built-in `Promise`, as it is the only way to capture the current
72+
`Variable` values and restore them later. This would limit the implementation
73+
of a user-land queueing.
74+
75+
```typescript
76+
const scheduler = {
77+
queue: [],
78+
postTask(task) {
79+
const { promise, resolve } = Promise.withResolvers();
80+
// Captures the current context by `Promise.prototype.then`.
81+
promise.then(() => {
82+
task();
83+
});
84+
// Defers the task execution by resolving the promise.
85+
queue.push(resolve);
86+
},
87+
runWhenIdle() {
88+
// LIMITATION: the tasks are not run synchronously.
89+
for (const cb of this.queue) {
90+
cb();
91+
}
92+
this.queue = [];
93+
}
94+
};
95+
```

0 commit comments

Comments
 (0)