[Question] about `await using`
CGQAQ opened this issue · comments
// an asynchronously-disposed, block-scoped resource
await using x = expr1; // resource w/ local binding
await using y = expr2, z = expr4; // multiple resources
- What is the
await
awaiting for? The@@asyncDispose
is executed when we go out of scope, isn't it? This seems changed the original behavior of theawait
keyword - Can I catch the error thrown in
@@asyncDispose
by wrapping theawait using
line in the try-catch - If
2
is not correct,x
's@@asyncDispose
throws an exception that will resulty
resource leak, how to avoid that?
- What is the
await
awaiting for? The@@asyncDispose
is executed when we go out of scope, isn't it? This seems changed the original behavior of theawait
keyword
The await
is an indicator that there is an implicit await
that will occur at the end of the block when the @@asyncDispose
method is invoked. This is why the await
indicator is tied to the using
declaration itself, much like the await
in for await
indicates one or more implicit await
s will be performed as part of the evaluation of for
.
This is consistent with for await
in that for await (const x of y)
does not await
the value of y
, but instead awaits certain steps within the AsyncIterator protocol.
- Can I catch the error thrown in
@@asyncDispose
by wrapping theawait using
line in the try-catch
Yes, though if an exception is thrown both from later statements in the block and from dispose, or from multiple disposals, the exception you will catch may be a SuppressedError
exception. This is intended to be an improvement over the try..finally
statements that using
/await using
would replace:
try {
const x = ...;
try {
...
throw new Error("A");
} finally {
await x.close();
}
} catch (e) {
// if `await x.close()` throws then `new Error("A")` is lost
}
vs
try {
using x = ...;
...
throw new Error("A");
} catch (e) {
// if `await x[Symbol.asyncDispose]()` throws then you get a `SuppressdError` whose
// `error` property is the exception from disposal and whose `suppressed` property is
// the error produced by `new Error("A")`, so neither exception is lost.
}
- If
2
is not correct,x
's@@asyncDispose
throws an exception that will resulty
resource leak, how to avoid that?
All resources and their cleanup steps are pushed onto a stack associated with the current lexical environment. When the block scope exits for any reason (i.e., due to throw
, return
, break
, continue
, or normal program execution continuing past the end of the block), all resources in the stack are disposed in reverse order. For an await using
that means that each resource is disposed after the previous disposal is await
-ed, even if the previous disposal results in an exception. In general, no resources added with using
or await using
should leak, aside from cases where execution is suspended and is never resumed (such as an unclosed generator or an async function waiting on a forever-pending Promise
).
Oh! thanks!
The await is an indicator that there is an implicit await that will occur at the end of the block when the @@asyncDispose method is invoked.
@rbuckton I hope I am not wasting your time with this reply as I don't have the high level knowledge as you guys do, but just wanted to express my 2 cents from the point of view of your regular Joe developer. This kind of change breaks existing norms and it seems counter-intuitive. I am sure that there are technical justifications, won't object that, but usually when you are using await
like this:
async function myFunction() {
const foo = await someAsyncFunction();
const bar = await someAsyncFunction();
}
you don't expect your program to reach bar
until foo
either fulfills or rejects to give the most basic example possible. await using
kinda breaks the norm here and it's misleading since you're not really waiting for anything, which is how most people understand the await
keyword.
All of this came to my attention from this video (link with relevant timestamp) on the proposal, so do give it a check if you like.
Have a great day/evening!