[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 await
ed, the yielded value is not itself getting await
ed 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:
- Created 2 years ago
- Comments:5 (5 by maintainers)
Thanks for the detailed explanation. So I guess another way to show the bug would be:
Looks like the codegen change was done in https://github.com/babel/babel/pull/13491 (with no helper change)