-
Notifications
You must be signed in to change notification settings - Fork 31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reduce unnecessary Awaits in blocks containing await using
#216
Conversation
A preview of this PR can be found at https://tc39.es/proposal-explicit-resource-management/pr/216. |
Hm. I would lightly prefer to always |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does what it says, though I'm not totally convinced by the semantics as mentioned above.
I can be convinced to only skip for |
As I mentioned on the issue, skipping new turn altogether in exceptional cases is not acceptable. The rule that should be respected is that if there is an |
It should be clarified that must execute is too strong here. If all disposals throw synchronously and the surrounding block is contained within a try {
const fail = () => { throw "sync throw" };
HAPPENS_BEFORE
await fail();
}
catch {
HAPPENS_AFTER
} Even with the Skipping the new turn in exceptional cases is directly in line with existing ECMASCript. |
Also, the current specification text skips new turns for synchronous exceptions, as do If that were not the case, This does, however, ensure that an |
I don't believe this is equivalent. In the To be more clear, if execution proceeded past the |
In the The closest example of this is In this example, the synchronous throw in So this is completely in line with the behavior of both That said, the most likely implementation of a normal Even with that though, a hand-written |
Per #196 (comment), this reduces the number of
Await
s in blocks containingawait using
and inAsyncDisposableStack
to only those necessary to meet the requirement that exiting a block in which anawait using
is evaluated should alwaysAwait
at least once, even if the resource defined byawait using
isnull
,undefined
, or synchronous disposable (i.e., an object with a@@dispose
instead of an@@asyncDispose
).As currently specified, the following code results in three
Await
s when only one is necessary:This was also the case in an
AsyncDisposableStack
containing synchronous disposables:This PR changes this behavior such that we only need an implicit
Await
in the following cases:await using
, to the disposal of a sync resource3 declared in ausing
.Example 1
Here,
B
has an implicitAwait
for its disposal, which means (per current proposal text):B[@@asyncDispose]()
is invoked in the same turn asHAPPENS_BEFORE
.A[@@dispose]()
is invoked in the following turn (unlessB[@@asyncDispose]()
threw synchronously).HAPPENS_AFTER
happens in the same turn asA[@@dispose]()
.Example 2
Here,
A
andB
both have implicitAwait
s, which means (per current proposal text):B[@@asyncDispose]()
is invoked in the same turn asHAPPENS_BEFORE
.A[@@dispose]()
is invoked in the following turn (unlessB[@@asyncDispose]()
threw synchronously).HAPPENS_AFTER
happens in the following turn (unlessA[@@dispose]()
threw synchronously).Example 3
Here, only
A
needs to have an implicitAwait
, sinceB
will dispose synchronously, which means (proposed change):B[@@dispose]()
is invoked in the same turn asHAPPENS_BEFORE
.A[@@asyncDispose]()
is invoked in the same turn asB[@@dispose]()
.HAPPENS_AFTER
happens in the following turn (unless bothB[@@dispose]()
andA[@@asyncDispose]()
threw synchronously).Note that this means that the above could all occur synchronously if both
B[@@dispose]()
andA[@@asyncDispose]()
were to throw synchronously, though that is consistent withawait
in general. However, ifA[@@asyncDispose]()
were to throw synchronously butB[@@dispose]()
were to complete synchronously we would need to introduce an implicitAwait
to ensure we account for the successful disposal.This PR also removes unnecessary
Await
s fromAsyncDisposableStack.prototype.disposeAsync()
since you must eitherawait
or invoke.then()
on the result to observe its effects.Fixes #196
Fixes #208
Footnotes
An async resource is an object with an
@@asyncDispose
method initialized in anawait using
declaration. ↩An sync-from-sync resource is either
null
,undefined
, or an object with a@@dispose
method (but not an@@asyncDispose
method) initialized in anawait using
declaration. ↩A sync resource is either
null
,undefined
, or an object with a@@dispose
method initialized in ausing
declaration. ↩