Description
This is my first time going through this proposal, and as I was looking at the following example, I couldn't help but feel like I was in callback hell again:
const asyncVar = new AsyncContext.Variable();
// Sets the current value to 'top', and executes the `main` function.
asyncVar.run("top", main);
function main() {
// AsyncContext.Variable is maintained through other platform queueing.
setTimeout(() => {
console.log(asyncVar.get()); // => 'top'
asyncVar.run("A", () => {
console.log(asyncVar.get()); // => 'A'
setTimeout(() => {
console.log(asyncVar.get()); // => 'A'
}, randomTimeout());
});
}, randomTimeout());
// AsyncContext.Variable runs can be nested.
asyncVar.run("B", () => {
console.log(asyncVar.get()); // => 'B'
setTimeout(() => {
console.log(asyncVar.get()); // => 'B'
}, randomTimeout());
});
}
I understand that this is probably not a practical example, but regardless, the proposal only offers a callback-style API. So it got me thinking about how we could flatten these callbacks, and I realized that there is an opportunity to interoperate with the Explicit Resource Management proposal. Specifically, AsyncContext.Variable
instances could also have a set()
method which returns a Promise that resolves to a disposable resource when the context value is updated, and upon disposing the resource, the context is changed back.
In the case of the example above, we would be able to clean it up like so:
const asyncVar = new AsyncContext.Variable();
// Sets the current value to 'top', and executes the `main` function.
asyncVar.run("top", main);
function main() {
// AsyncContext.Variable is maintained through other platform queueing.
setTimeout(() => {
console.log(asyncVar.get()); // => 'top'
using _ = await asyncVar.set("A");
console.log(asyncVar.get()); // => 'A'
setTimeout(() => {
console.log(asyncVar.get()); // => 'A'
}, randomTimeout());
}, randomTimeout());
// AsyncContext.Variable runs can be nested.
using _ = await asyncVar.set("B");
console.log(asyncVar.get()); // => 'B'
setTimeout(() => {
console.log(asyncVar.get()); // => 'B'
}, randomTimeout());
}
To be clear, the lifetime of an AsyncContext value using set()
isn't quite semantically equivalent to using run()
, but this idea is more about readability and exposing interoperability with other patterns.
It's a pretty nitpicky suggestion, but I'd be interested in hearing what other people think about it.