Closed cowboyd closed 8 months ago
I have the same need for CanvasRenderingContext2D
class CanvasRenderingContext2DState {
#context
constructor(context) {
this.#context = context
context.save()
}
[Symbol.dispose]() {
this.#context.restore()
}
}
{
using new CanvasRenderingContext2DState(context);
{
using new CanvasRenderingContext2DState(context);
}
}
OR
const saveState = (context) => {
context.save()
return {[Symbol.dispose]: () => context.restore()}
}
{
using saveState(context);
{
using saveState(context);
}
}
We previously were considering void
bindings as part of this proposal, and the topic has come up in proposals like pattern matching as well. With void
bindings, you might write this instead:
{
using void = saveState(context);
{
using void = saveState(context);
}
}
It's highly likely that void
bindings will be proposed as a standalone feature in the future, so we are not including it in this proposal. In the meantime, you would need to use temporary variables (which might require lint rule overrides for some linters):
{
using _ = saveState(context);
{
using _1 = saveState(context);
using _2 = someOtherUnusedDisposable();
// etc.
}
}
For context, void
bindings are also being considered for pattern matching as a means to introduce an explicit "discard" pattern that would be consistent in both pattern matching and destructuring:
obj is { x: void, y: void }; // obj must have 'x' and 'y' properties, the values don't matter
const { x: void, y: void } = obj; // reads 'x' and 'y' (possibly only for side effects)
using void = new UniqueLock(mutex); // discards binding for lock since it isn't otherwise referenced
const Point(void, y) = obj; // extractors
const [void, a, b] = ar; // explicit discard marker instead of an elision
Maybe too late to change to
{
using saveState(context);
}
{
const handler = using saveState(context);
}
?
With current proposal, maybe an empty deconstructing can also be used
{
using {} = saveState(context);
}
{
using [] = saveState(context);
}
Maybe too late to change to [...]
This is something we have decided we are strongly opposed to supporting. For await using
declarations, it would bury the fact that there is an implicit await
at the end of the block by having await using
appear at an arbitrary position within an expression. Instead await using
must appear as a statement within the block to make it easier to recognize. Since we cannot support it for await using
, we would not support it for using
as well, so that the syntax remains consistent.
Additionally, using
in an expression position would run afoul of ambiguity if the resource you want to track comes from a parenthesized expression as using(foo)
is already a legal call expression.
However, if you do need to track a resource inside of another expression, you should be able to use DisposableStack
instead:
{
using stack = new DisposableStack();
const handler = stack.use(saveState(context));
}
This is essentially the same as what a pure expression-based using
might accomplish, but aligns with the requirements for await using
:
async function f() {
{
await using astack = new AsyncDisposableStack();
const handler = astack.use(saveState(context));
...
} // implicit `await stack[Symbol.asyncDispose]()`
}
With current proposal, maybe an empty deconstructing can also be used [...]
Destructuring is expressly forbidden by using
declarations. In addition const {} = x
and const void = x
differ in that const {} = x
requires that x
is neither null
nor undefined
, as neither can be destructured.
To follow up to my earlier comment, a proposal to add void
Discard Bindings is currently on the docket for the next TC39 plenary.
using void = expr()
should address the general case in the OP, so I am closing this issue. If you are interested in additional discussion or follow up, I would suggest opening an issue in the void
Discard Bindings proposal repo.
Just to close the loop here, Discard/void
bindings are now a Stage 1 proposal: https://github.com/tc39/proposal-discard-binding
I'm thinking of the case where you want to start a resource so that it can be running, but then it gets shut down when it passes out of scope. As such, you're executing it for its side effects, and not to actually use a handle
I don't need to use the value returned by
createServer()
. I just need the server running and handling requests within the given scope, and to be disposed when it passes out of that scope.