using await
/await using
/async using
#12
Description
In the January, 2023 TC39 plenary session there was a debate on what the final syntax for the asynchronous using
declaration should be, given that there are conflicting developer intuitions regarding each potential syntax form.
The three forms currently under consideration are:
using await x = y
(currently proposed)async using x = y
await using x = y
We opted to postpone advancement until the March, 2023 plenary to give some delegates the opportunity to conduct informal surveys to determine which of these options more clearly represented the semantics of the proposal. If there is no clear choice, the proposal will advance to Stage 3 at the March, 2023 plenary using the proposed syntax.
There are pros and cons to each syntax option, which I will summarize below.
using await x = y
The current proposal syntax was chosen prior to the January, 2023 plenary for a number of reasons. The use of the await
keyword very clearly indicates an asynchronous interleaving point, much like AwaitExpression and the for-await-of
statement. In addition, the using await
keyword order was chosen to avoid ambiguity with an await using
expression, given that using
on its own remains a valid identifier, and that await
as a modifier currently only occurs to the right of the statement it modifies (i.e., for await
).
However, there is the potential for confusion for code readers due to the non-local nature of the using
declaration. The actual await
occurs as control flow exits the block scope, rather than immediately at the site of the declaration. This may lead some to incorrectly assume that using await x = y
would await the value of x
/y
. In the case of a for await (const x of y)
statement, the for await
operation doesn't await
the value of y
, though it does await
the result of each next()
operation on its iterator, which in turn means that it does await
the value that becomes x
. This potential conflation of meanings can lead to confusion if attempting to intuit the meaning of using await
given for await
as context.
async using x = y
The async using
syntax form was initially suggested as a means to pair with a specially annotated await using { ... }
block, so as to resolve #1. However, we were able to resolve #1 without the introduction of a new block syntax, which is why we did not choose prior to the January, 2023 plenary.
The advantage of the async using
syntax is that it indicates an asynchronous operation without conveying a meaning that the operation might occur immediately. However, async using
breaks with existing uses of async
in the language today. Currently, async
is only used to indicate functions that have different runtime semantics than a normal function, permitting the use of await
expressions and the for-await-of
statement in the function body. Yet this use doesn't indicate an interleaving point will occur, which breaks from the intended semantics of this proposal. This meaning of async
is further reinforced by proposals like async do {}
, which would still require an explicit await
somewhere to observe the result.
Also, while a minor concern, async using
will likely need a cover grammar to disambiguate between an async using
declaration and an async using =>
declaration.
await using x = y
A third option we've discussed outside of plenary would be to use an await using
ordering instead. This matches the C# syntax that is the equivalent of this behavior, and has the benefit of continuing to use await
to indicate an async interleaving point. This also has a slight advantage over using await
, given that the keyword order may lean more towards an interpretation of "await
the using
of x
". It is unclear, however, if this distinction is enough to guide developer intuition.
As with async using
, there is a minor concern regarding the need for a potential cover grammar to disambiguate await using
as a declaration from await using
(or await using.x
) as an expression.
Not considered: using async x = y
We are not currently considering a using async
keyword order at this time. We feel it is important to align the sync and async versions of the proposal, and are concerned that the ambiguity between:
using async x = y; // async 'using' of 'x' identifier
using async = y; // sync 'using' of 'async' identifier
would lead to further confusion. We could ban async
as an identifier in using
, as we've currently done for await
(and for the same reason), however there is a distinction between await
and async
as identifiers. Currently, await
is a reserved word, making it a syntax error to use it as an identifier in strict-mode code. It is also a syntax error to use it as an identifier in an async function, leaving it as only legal in non-async, non-strict code. async
, on the other hand, is not reserved in any mode, nor is it reserved in async functions, so we are concerned that banning async
for this purpose would get in the way of potential refactors in existing code, such as:
// source
const async = ...;
try {
...
}
finally {
async.dispose();
}
// refactored
using async_1 = ...;
...
Due to the existing restrictions the language imposes on the use of await
as an Identifier, we're far less concerned about using await
being a refactoring hazard.