tc39/proposal-explicit-resource-management

Possibilities for Other Syntax?

fixiabis opened this issue ยท 17 comments

I have referred to the content mentioned in #186, #15.

Is it possible to express it using the following code?

const resource = getResource() finally dispose;
const asyncResource = (await getAsyncResource()) finally await dispose;

Not entirely certain about potential concerns, but would the introduction of a finally dispose / finally await dispose syntax like this help improve the situation?

Not a fan of current disposal syntax at all especially the async one, it looks very confusing.

Especially the await using syntax is extremely confusing given that it introduces a completely different meaning to an existing await keyword. I really hope that this gets reconsidered before mass adoption

dhkatz commented

I would much prefer async using instead of await using. I feel like await implies a promise that is being resolved but that is not the case.

This seems very confusing to me:

await using connection= await db.connect()

The first await on the right is resolving a promise, blocking execution. The second await is not blocking execution.

Compared to:

async using connection = await db.connect()

To me this conveys a bit clearer that the left side will be handled asynchronously during/after execution

This idea is inspired by TypeScript as keyword. Therefore, in my imagination, the following code would also be feasible:

(getResource() as DisposibleResource finally dispose); // returns value, would be dispose when leave scope

I've been playing with resource management quite a bit using it's inclusion in TypeScript.

Even after building some familiarity and muscle memory the two await keywords in an asynchronous creation of an AsyncResource feels wrong. I often forget to place one of the two required await keywords and am only pushed back onto the happy path thanks to TypeScript's language server.

I think the await using syntax is fundamentally at odds with the rest of the naming in the proposal: This syntax is to indicate that you're using an async resource, not an await resource.

To align the syntax with my intuition and a prose description of what's going on, consider:

I would like to use an async resource called foo that I've obtained by awaiting the result of this function call.

using async foo = await thisFunctionCall();

I think the key difference here is that we're 'using an async resource' which translates directly to the syntax using async. When using a sync resource, we are just using it.

I had the same experience: I read the following line here and did not understand it.

await using file = await openFile("dist/test.txt", "w");

await doesnโ€™t feel like a modifier to me. Either of the following two lines would have been easier to understand:

async using file = await openFile("dist/test.txt", "w");
using async file = await openFile("dist/test.txt", "w");

To add to this, the prefix of await currently guarantees that execution will yield to the microtask queue. If the current syntax were preserved, that rule would no longer hold and every function using the syntax will be harder to understand.

Are there technical reasons that motivate the current async-prefixed syntax? Some parsing / backtracking trickiness? Could there be a path where the current syntax introduced in TypeScript remains supported over a deprecation period and an 'improved' syntax coexists with it?

I really like @fixiabis's proposal to reuse finally as a suffix here, as it has similar runtime semantics as a finally block, the intended meaning is immediately obvious.

If it doesn't fly for some reason, I much prefer async using ... then using async to await, per @ggoodman's argument.

I had the same experience: I read the following line here and did not understand it.

await using file = await openFile("dist/test.txt", "w");

await doesnโ€™t feel like a modifier to me. Either of the following two lines would have been easier to understand:

async using file = await openFile("dist/test.txt", "w");
using async file = await openFile("dist/test.txt", "w");

We discussed a number of syntax alternatives in plenary, including await using ..., using await ..., async using ..., and using async ..., and in the end we settled on await using ... which is what we advanced to stage 3.

await using has several advantages to it:

  1. It is consistent with C#'s syntax, which was the prior art that async/await was originally derived from.
  2. Since it comes before the using keyword, it is easily recognized when a reader visually scans an indented code block.
  3. Not only implies an await occurs, but by having it prefix the using keyword it indicates that what is awaited is the effect of the using keyword, not the initializer.

I was opposed to async using as the async keyword does not imply an await will occur, it implies a function allows the use of the await keyword. Repurposing it for use with using would be inconsistent with the language.

await doesnโ€™t feel like a modifier to me.

await has also been a modifier since the introduction of for await.

Especially the await using syntax is extremely confusing given that it introduces a completely different meaning to an existing await keyword. I really hope that this gets reconsidered before mass adoption

I disagree that it gives a different meaning to await. There are two cases of await in JS today: AwaitExpression, and for await. The meaning here is closer to for await, which has an implicit await per iteration. And, just like await using x = await y, there are times where you will have to write for await (const x of await y) in code today, because for await does not implicitly await the expression you are about to iterate.

This idea is inspired by TypeScript as keyword. Therefore, in my imagination, the following code would also be feasible:

(getResource() as DisposibleResource finally dispose); // returns value, would be dispose when leave scope

This is something that would never reach consensus. It was made clear early on that anything that would introduce end-of-block cleanup needs to be declared at the statement level, not at the expression level.

To add to this, the prefix of await currently guarantees that execution will yield to the microtask queue. If the current syntax were preserved, that rule would no longer hold and every function using the syntax will be harder to understand.

I think this is a slightly incorrect interpretation of await. As I've said in another comment, for await does not precisely guarantee execution will yield to the microtask queue, but rather that it will yield at the beginning of each iteration, which coincidently is also the end of each iteration. await using also yields to the microtask queue, but at the end of the block.

Are there technical reasons that motivate the current async-prefixed syntax? Some parsing / backtracking trickiness? Could there be a path where the current syntax introduced in TypeScript remains supported over a deprecation period and an 'improved' syntax coexists with it?

The technical reason is that you need to declare that an await will occur during cleanup at the time the resource is initialized, as that determines whether to use [Symbol.dispose] vs [Symbol.asyncDispose] for cleanup. That indication needs to be clearly marked at the front of the declaration, and at statement level, so that it can be readily recognized by code reviewers reading code containing an await using declaration.

If it doesn't fly for some reason, I much prefer async using ... then using async to await, per @ggoodman's argument.

We have discussed all of these options in plenary over the course of multiple meetings and settled on await using. We conducted a number of informal polls, both publicly and internally within several TC39 member organizations and found that the percentage of respondents that favored async using and the percentage of respondents that favored await using were almost identical, with a slightly larger number favoring the latter. Whichever syntax we chose, this would be new syntax that users would need to educate themselves on before using. Now that this proposal has reached stage 3, I don't expect the syntax to change without a very strong reason above and beyond what's already been discussed in plenary.

Closing as this merely rehashes a settled debate in plenary, which already reached consensus in await using.

Thanks for taking the ideas into consideration. Can't wait for this to be available in engines!