Skip to content

Commit 5dae578

Browse files
authored
Add two FAQs about alternative snapshots to the README (#96)
1 parent 3243915 commit 5dae578

File tree

1 file changed

+64
-0
lines changed

1 file changed

+64
-0
lines changed

README.md

+64
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,70 @@ asyncVar.run("A", async () => {
534534
This increases the integrity of async context variables, and makes them
535535
easier to reason about where a value of an async variable comes from.
536536

537+
## How does `AsyncContext` interact with built-in schedulers?
538+
539+
Any time a scheduler (such as `setTimeout`, `addEventListener`, or
540+
`Promise.prototype.then`) runs a user-provided callback, it must choose which
541+
snapshot to run it in. While userland schedulers are free to make any choice
542+
here, this proposal adopts a convention that built-in schedulers will always run
543+
callbacks in the snapshot that was active when the callback was passed to the
544+
built-in (i.e. at "registration time"). This is equivalent to what would happen
545+
if the user explicitly called `AsyncContext.Snapshot.wrap` on all callbacks
546+
before passing them.
547+
548+
This choice is the most consistent with the function-scoped structure that
549+
results from `run` taking a function, and is also the most clearly-defined
550+
option among the possible alternatives. For instance, many event listeners
551+
may be initiated either programmatically or through user interaction; in the
552+
former case there may be a more recently relevant snapshot available, but it's
553+
inconsistent across different types of events or even different instances of the
554+
same type of event. On the other hand, passing a callback to a built-in function
555+
happens at a very clearly defined time.
556+
557+
Another advantage of registration-time snapshotting is that it is expected to
558+
reduce the amount of intervention required to opt out of the default snapshot.
559+
Because `AsyncContext` is a subtle feature, it's not reasonable to expect every
560+
web developer to build a complete understanding of its nuances. Moreover, it's
561+
important that library users should not need to be aware of the nature of the
562+
variables that library implementations are implicitly passing around. It would
563+
be harmful if common practices emerged that developers felt they needed to wrap
564+
their callbacks before passing them anywhere. The primary means to have a
565+
function run in a different snapshot is to call `Snapshot.wrap`, but this
566+
will be idempotent when passing callbacks to built-ins, making it both less
567+
likely for this common practice to begin in the first place, and also less
568+
harmful when it does happen unnecessarily.
569+
570+
## What if I need access to the snapshot from a more recent cause?
571+
572+
The downside to registration-time snapshotting is that it's impossible to opt
573+
_out_ of the snapshot restoration to access whatever the snapshot would have
574+
been _before_ it was restored. Use cases where this snapshot is more relevant
575+
include
576+
577+
- programmatically-dispatched events whose handlers are installed at application
578+
initialization time
579+
- unhandled rejection handlers are a specific example of the above
580+
- tracing execution flow, where one task "follows from" a sibling task
581+
582+
As explained above, the alternative snapshot choices are much more specific to
583+
the individual use case, but they can be made available through side channels.
584+
For instance, web specifications could include that certain event types will
585+
expose an `originSnapshot` property (actual name to be determined) on the event
586+
object containing the active `AsyncContext.Snapshot` from a specific point in
587+
time that initiated the event.
588+
589+
Providing these additional snapshots through side channels has several benefits
590+
over switching to them by default, or via a generalized "previous snapshot"
591+
mechanism:
592+
593+
- different types of schedulers may have a variety of potential origination
594+
points, whose scope can be matched precisely with a well-specified side
595+
channel
596+
- access via a known side channel avoids loss of idempotency when callbacks are
597+
wrapped multiple times (whereas a "previous snapshot" would becomes much less
598+
clear)
599+
- no single wrapper method for developers to build bad habits around
600+
537601
# Prior Arts
538602

539603
## zones.js

0 commit comments

Comments
 (0)