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

Bug in async-await transformer when using async generators #2497

Closed
jridgewell opened this issue Aug 28, 2022 · 1 comment
Closed

Bug in async-await transformer when using async generators #2497

jridgewell opened this issue Aug 28, 2022 · 1 comment

Comments

@jridgewell
Copy link
Contributor

I'm using esbuild to transform async/await into a promise+generator to support async zone support (we use a patched Promise.prototype.then to snapshot/restore the async zone). I'm passing --supported:async-await=false, which works correctly for regular async functions. But it transforms async generators into an incorrect result.

The __async helper is consuming the entire generator without yielding to the caller when it encounters a yield in the wrapped async generator. I later realized that setting --support:async-generator=false will actually throw an error at build time because downleveling is unsupported. I think transforming async generators when async-await=false is a bug, but ideally I'd like to see support for downleveling async generators.

// input.js
async function* foo() {
  await Promise.resolve(1);

  const [a, b] = yield 2;
  return a + b;
}

async function run() {
  const g = foo();

  let next = await g.next();
  console.assert(next.value === 2);
  console.assert(next.done === false);

  next = await g.next([1, 2]);
  console.assert(next.value === 3);
  console.assert(next.done === true);
}

run();
// output.js
var __async = (__this, __arguments, generator) => {
  return new Promise((resolve, reject) => {
    var fulfilled = (value) => {
      try {
        step(generator.next(value));
      } catch (e) {
        reject(e);
      }
    };
    var rejected = (value) => {
      try {
        step(generator.throw(value));
      } catch (e) {
        reject(e);
      }
    };
    var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
    step((generator = generator.apply(__this, __arguments)).next());
  });
};
function* foo() {
  return __async(this, null, function* () {
    yield Promise.resolve(1);
    const [a, b] = yield 2;
    return a + b;
  });
}
function run() {
  return __async(this, null, function* () {
    const g = foo();
    let next = yield g.next();
    console.assert(next.value === 2);
    console.assert(next.done === false);
    next = yield g.next([1, 2]);
    console.assert(next.value === 3);
    console.assert(next.done === true);
  });
}
run();
@evanw
Copy link
Owner

evanw commented Aug 28, 2022

This bug is due to you telling esbuild that async generator functions are supported natively but that async isn't supported natively, which is nonsensical. I can fix the bug to make this a build error so that esbuild no longer lets you do this.

@evanw evanw closed this as completed in 501abf7 Aug 29, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants