Skip to content
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

Should an await using for sync dispose Await the return value or just Await undefined #179

Closed
rbuckton opened this issue Jun 30, 2023 · 6 comments · Fixed by #180
Closed

Comments

@rbuckton
Copy link
Collaborator

rbuckton commented Jun 30, 2023

While we intend to maintain the consensus that an evaluated await using will always imply an implicit Await at the end of the block, it was suggested that the return value of a synchronous [Symbol.dispose]() method shouldn't itself be Await-ed, even if that method returns a Promise so as to remain consistent with the synchronous using statement.

The following is are examples of the current behavior and the suggested behavior:

Current behavior

{
  using x = { [Symbol.dispose]() { return Promise.reject(new Error("x")); } };

} // return value ignored, does not throw on block exit

{
  await using y = { [Symbol.dispose]() { return Promise.reject(new Error("y")); } };

} // implicit `await` for return value, throws Error("y") on block exit

Suggested behavior

{
  using x = { [Symbol.dispose]() { return Promise.reject(new Error("x")); } };

} // return value ignored, does not throw on block exit

{
  await using y = { [Symbol.dispose]() { return Promise.reject(new Error("y")); } };

} // implicit `await undefined`, return value ignored, does not throw on block exit
@ljharb
Copy link
Member

ljharb commented Jun 30, 2023

It says await using, how is that not an explicit await?

@mhofman
Copy link
Member

mhofman commented Jun 30, 2023

@ljharb if the resource implements Symbol.dispose returning a promise, it is not an async resource. An async resource is an object implementing Symbol.asyncDispose.

The precedent here is for await (const foo of syncIterable). In that case I believe the async iterator wrapper does adopt a promise returned by the sync iterable.

Whether to adopt that precedent or not is the question. It really begs the question of why a sync resource might return a promise from its Symbol.dispose method in the first place.

@ljharb
Copy link
Member

ljharb commented Jun 30, 2023

I agree that would be strange, but I don't see a strong argument to deviate from precedent. If I await a non-promise object it's still awaited.

@rbuckton
Copy link
Collaborator Author

@ljharb if the resource implements Symbol.dispose returning a promise, it is not an async resource. An async resource is an object implementing Symbol.asyncDispose.

The precedent here is for await (const foo of syncIterable). In that case I believe the async iterator wrapper does adopt a promise returned by the sync iterable.

Whether to adopt that precedent or not is the question. It really begs the question of why a sync resource might return a promise from its Symbol.dispose method in the first place.

for-await is a bit complicated, but there are some parts of the Iterator protocol that are not awaited as part of AsyncFromSyncIterator, such as the call to next(). As a result, the precedent isn't actually very clear.

@nicolo-ribaudo
Copy link
Member

I agree that it should not await, as I suggested in tc39/proposal-async-explicit-resource-management#17.

Conceptually, the default implementation of [@@asyncDispose] should be

[@@asyncDispose]() {
  this[@@dispose]();
}

and not

[@@asyncDispose]() {
  return this[@@dispose]();
}

given that there is no expected return value.

@benjamingr
Copy link

My expectation would be to not await:

const o = { 
    [Symbol.asyncIterator]() {
      return async function* () {
        for(let i = 0; i < 10; i++) yield i;
      }();
    }
}
let breaker = 0;
for await (const item of o) {
  console.log(item);
  if(breaker++>100) break;
}

This logs 0..9 but:

const o = { 
    [Symbol.iterator]() {
      return async function* () {
        for(let i = 0; i < 10; i++) yield i;
      }();
    }
}
let breaker = 0;
for await (const item of o) {
  console.log(item);
  if(breaker++>100) break;
}

This logs undefined 100 times.

It would be weird for await using to await the return value from a user expectation and language consistency PoV in my opinion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
5 participants