Skip to content

[Bug]: Regression in for await transform, is not awaiting iterator values #13811

@Jessidhia

Description

@Jessidhia

💻

  • 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 #13811 (comment))

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

Metadata

Metadata

Labels

i: needs triageoutdatedA closed issue/PR that is archived due to age. Recommended to make a new issue

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions