tc39 / proposal-explicit-resource-management

ECMAScript Explicit Resource Management
https://arai-a.github.io/ecma262-compare/?pr=3000
BSD 3-Clause "New" or "Revised" License
758 stars 30 forks source link

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

Closed rbuckton closed 1 year ago

rbuckton commented 1 year ago

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 commented 1 year ago

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

mhofman commented 1 year ago

@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 commented 1 year ago

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 commented 1 year ago

@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 commented 1 year ago

I agree that it should not await, as I suggested in https://github.com/tc39/proposal-async-explicit-resource-management/issues/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 commented 1 year ago

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.