You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copied from tc39/proposal-async-context#113 with the only change being the title, dynamic using to a more readable with, and me combining the first two comments.
Click to expand
I found a way to make everything but the variable get fast in [this comment](https://github.com/tc39/proposal-async-context/issues/107#issuecomment-2622966231), and that one remaining optimization looks an awful lot like the inline caches used for types. The fallback path is about the only part that isn't a trivially inlined function.
And for semantics, there's concerns about excessive creation of async variables, and I do share them: tc39/proposal-async-context#50
One way to quietly guide people to use async vars correctly (and to optimize async var usage in general) is to make them literal variable bindings. Also, admittedly, .get() is annoying boilerplate.
And for performance, this glides right in and makes it all both trivially tree-shakeable and essentially zero-cost.
So, what about this syntax?
The syntax here is intentionally somewhat anti-bikeshed. Concise enough to get the point across, but (especially for snapshots) ugly enough to move discussion along.
// Define one or more dynamic variabledynamicfoo=initialValue,bar= ...
// Get the variable's current valueletcurrentValue=foo// Set a dynamic variable in a scope// Automatically restored after exit{withfoo=otherValuewithpath.to.foo=otherValue// ...}// Get a snapshot of the current state// Semantically changes on dynamic set, but engines can amortize thatconstsnapshot=function.context// Use snapshotwith*fromsnapshot
From a spec standpoint, this would be a unique type of reference, alongside property values. Module property accesses would return dynamic values as references to dynamic values.
To show how this all would play out in practice, here's most of the README examples:
dynamicasyncVar;// Sets the current value to 'top', and executes the `main` function.{withasyncVar="top";main()}functionmain(){// Dynamic variable is maintained through other platform queueing.setTimeout(()=>{console.log(asyncVar);// => 'top'{withasyncVar='A'console.log(asyncVar);// => 'A'setTimeout(()=>{console.log(asyncVar);// => 'A'},randomTimeout());}},randomTimeout());// Dynamic variable runs can be nested.{withasyncVar="B";console.log(asyncVar);// => 'B'setTimeout(()=>{console.log(asyncVar);// => 'B'},randomTimeout());}// Dynamic variable was restored after the previous run.console.log(asyncVar);// => 'top'}functionrandomTimeout(){returnMath.random()*1000;}
dynamicasyncVar;letsnapshot;{withasyncVar="A";// Captures the state of all dynamic variables at this moment.snapshot=function.context;}{withasyncVar="B";console.log(asyncVar);// => 'B'// The snapshot will restore all dynamic variables to their snapshot// state and invoke the wrapped function. We pass a function which it will// invoke.with*fromsnapshot;// Despite being lexically nested inside 'B', the snapshot restored us to// to the snapshot 'A' state.console.log(asyncVar);// => 'A'}
letqueue=[];exportfunctionenqueueCallback(cb: ()=>void){// Each callback is stored with the context at which it was enqueued.constsnapshot=function.context;queue.push({snapshot, cb});}runWhenIdle(()=>{// All callbacks in the queue would be run with the current context if they// hadn't been wrapped.for(const{snapshot, cb}ofqueue){with*fromsnapshot;cb();}queue=[];});
dynamiccurrentTask={priority: "default"};constscheduler={postTask(task,options){// In practice, the task execution may be deferred.// Here we simply run the task immediately.withcurrentTask={priority: options.priority};task();},currentTask(){returncurrentTask;},};constres=awaitscheduler.postTask(task,{priority: "background"});console.log(res);asyncfunctiontask(){// Fetch remains background priority by referring to scheduler.currentTask().constresp=awaitfetch("/hello");consttext=awaitresp.text();scheduler.currentTask();// => { priority: 'background' }returndoStuffs(text);}asyncfunctiondoStuffs(text){// Some async calculation...returntext;}
The choice of keyword could also of course be changed to something like with, like with currentTask = { priority: options.priority };.
But in any case, there's unique advantages to using dedicated syntax:
using foo = asyncVar.withValue(new Resource()) precludes stuff like using with foo = new Resource() simultaneously setting the async variable and cleaning it up as it leaves scope.
Using static syntax allows engines to batch their insertion and cleanup. If they use arrays instead of linked lists, this could result in significant perf wins in JIT-compiled code.
Using static syntax means far fewer inline caches needed. All that's needed is a single type check per variable, something very helpful for high-performance libraries and frameworks that need to set async context in perf-critical spots.
The text was updated successfully, but these errors were encountered:
Copied from tc39/proposal-async-context#113 with the only change being the title,
dynamic using
to a more readablewith
, and me combining the first two comments.Click to expand
I found a way to make everything but the variable get fast in [this comment](https://github.com/tc39/proposal-async-context/issues/107#issuecomment-2622966231), and that one remaining optimization looks an awful lot like the inline caches used for types. The fallback path is about the only part that isn't a trivially inlined function.And for semantics, there's concerns about excessive creation of async variables, and I do share them: tc39/proposal-async-context#50
One way to quietly guide people to use async vars correctly (and to optimize async var usage in general) is to make them literal variable bindings. Also, admittedly,
.get()
is annoying boilerplate.And for performance, this glides right in and makes it all both trivially tree-shakeable and essentially zero-cost.
So, what about this syntax?
From a spec standpoint, this would be a unique type of reference, alongside property values. Module property accesses would return dynamic values as references to dynamic values.
To show how this all would play out in practice, here's most of the README examples:
The choice of keyword could also of course be changed to something like
with
, likewith currentTask = { priority: options.priority };
.But in any case, there's unique advantages to using dedicated syntax:
using foo = asyncVar.withValue(new Resource())
precludes stuff likeusing with foo = new Resource()
simultaneously setting the async variable and cleaning it up as it leaves scope.The text was updated successfully, but these errors were encountered: