question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. ItĀ collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[Bug]: Regression in `for await` transform, is not `await`ing iterator values

See original GitHub issue

šŸ’»

  • Would you like to work on a fix?

How are you using Babel?

babel-loader (webpack)

Input code

Minimal example that shows the compilation issue:

async function f() {
  for await (const x of [].push()) {}
}

More elaborate example that crashes at runtime if f is invoked:

async function f() {
  for await (const { foo: { bar } } of [1].map(async x => ({ foo: { bar: x } }))) {}
}

Configuration file name

babel.config.js

Configuration

https://babeljs.io/repl defaults with not ie 11 in ā€œtargetsā€ changed to ie 11

Current and expected behavior

(This section was a red herring as the codegen change was intended, the actual issue is that the AsyncFromSyncIterator behavior was not updated to match the new codegen; see https://github.com/babel/babel/issues/13811#issuecomment-934022118)

Babel is miscompiling the sample for await loop in such a way that it’s effectively building a regular for of loop on top of regenerator. While the iterator.next() call is being awaited, the yielded value is not itself getting awaited as it’s supposed to, so the looped over values are Promise instances instead of their values.

This is a regression from (at least) 7.13. The REPL doesn’t seem to have 7.14 so I couldn’t check that.

Relevant annotated output in 7.13.17:

        case 4:
          _context.next = 6;
          // await iterator.next()
          return regeneratorRuntime.awrap(_iterator.next());

        case 6:
          _step = _context.sent;
          // store whether iterator is complete but don't check yet to not leak the potential Promise 
          _iteratorNormalCompletion = _step.done;
          _context.next = 10;
          // await step.value (what was yielded from iterator.next())
          return regeneratorRuntime.awrap(_step.value);

        case 10:
          // read the result of await step.value
          _value = _context.sent;

          // check now for iterator completion
          if (_iteratorNormalCompletion) {
            _context.next = 16;
            break;
          }

          // set the loop variable to the awaited iterator.value
          x = _value;

Relevant annotated output in 7.15.7:

        case 4:
          _context.next = 6;
          // await iterator.next()
          return regeneratorRuntime.awrap(_iterator.next());

        case 6:
          // directly check if iterator is done before awaiting for value
          if (!(_iteratorAbruptCompletion = !(_step = _context.sent).done)) {
            _context.next = 11;
            break;
          }

          // directly uses step.value, which at this point is a Promise instead of the yielded value
          x = _step.value;
          // Promise is leaked if user doesn't redundantly await the value inside the loop

Environment

  System:
    OS: Linux 5.10 Ubuntu 20.04.3 LTS (Focal Fossa)
  Binaries:
    Node: 16.5.0 - ~/.nodenv/versions/16.5.0/bin/node
    Yarn: 3.0.0 - ~/.nodenv/versions/16.5.0/bin/yarn
    npm: 7.19.1 - ~/.nodenv/versions/16.5.0/bin/npm
  Monorepos:
    Yarn Workspaces: 3.0.0
  npmPackages:
    @babel/core: ^7.15.5 => 7.15.5
    @babel/runtime: ^7.15.4 => 7.15.4
    jest: ^27.0.6 => 27.0.6

Possible solution

No response

Additional context

No response

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:5 (5 by maintainers)

github_iconTop GitHub Comments

2reactions
lightmarecommented, Oct 5, 2021

Thanks for the detailed explanation. So I guess another way to show the bug would be:

async function* bar() {
  yield* [Promise.resolve("ok")] // CreateAsyncFromSyncIterator
}

bar().next().then(console.log);
// should print { value: 'ok', done: false }
0reactions
Jessidhiacommented, Oct 5, 2021

Looks like the codegen change was done in https://github.com/babel/babel/pull/13491 (with no helper change)

Read more comments on GitHub >

github_iconTop Results From Across the Web

Await inside for loop is admitted in Dart? - Stack Overflow
Yes, await is permitted inside a for loop in Dart, and it will work as expected. ... This can help you catch many...
Read more >
for await...of - JavaScript - MDN Web Docs
When a for await...of loop iterates over an iterable, it first gets the iterable's [@@asyncIterator]() method and calls it, which returns anĀ ...
Read more >
Bug Patterns - Error Prone
Bug patterns. This list is auto-generated from our sources. Each bug pattern includes code examples of both positive and negative cases; these examples...
Read more >
object is not iterable (cannot read property symbol(symbol.iterator ...
This is because Promise.all() accepts a single argument, which must be an iterable of Promises, such as an Array . Passing an array,...
Read more >
Are we async yet?
Are we async yet? Yes!. The long-awaited async / await syntax has been stabilized in Rust 1.39. You can use it with the...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found