microsoft / microsoft/TypeScript
ES2017 Async Generator Suspended on `yield*` Continues Execution When Returned
@rbuckton is already working on this.
Since Jan 30, 2025.
- Dominant language
- Go
- Stars
- 111k
- Forks
- 14.3k
- Avg merge
- 2d 4h
- Merged PRs (30d)
- 132
Description
### 🔎 Search Terms
es2017 async generator delegator await loop yield*
### 🕗 Version & Regression Information
- This is the behavior in every version I tried, and I reviewed the FAQ for entries about async generators
### ⏯ Playground Link
https://www.typescriptlang.org/play/?declaration=false&module=0&ts=5.8.0-dev.20250122#code/PQgEB4CcFMDNpgOwMbVAGwJYCMC8AiaAZ0WgA8AXfUYAPgCh6AKAQyIE8VQmBKUXWqADe9UGJrBQAQQAmLAA4VoM0LEgB7ALYAuUeLAALChXlFtIAO5WAdBXbziySJkXoWiAObX1kD8BnqyETABu4y2OrqANbAMOjQbNAAtIjqSsF2DkROLhRJAExJAMzWRproemJsnMiqAK4oFJjqiABUoB68wpXiYuyY0Ogy7awcXLANyE0tI3wivQvi-YMqAIwA3D29AL48vJtb4sgtROrx1ujqnfgssEqQ+DybO4y9x4hEFKCYX7gdvABtADK7E0EXQ1mqKAAkvcWBQfABdfY9d6nc6XTosCwsH7fCjWUiUXhPHrY3FfH7WGAUOqQRAAfmsKN2KKAA
### 💻 Code
```ts
///
(async () => {
// Adapted from:
// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-3.html
async function* g() {
yield* (async function* () {
yield 1;
})();
console.log("after");
}
const it = g()[Symbol.asyncIterator]();
console.log(await it.next());
await it.return?.();
})();
```
### 🙁 Actual behavior
If you run the provided TS code directly in a modern Node process, you get the expected following output:
```
{ value: 1, done: false }
```
If you instead run the JS output code, you get:
```
{ value: 1, done: false }
after
```
Here, the downleveled code behaves differently than the source and continues execution after `.return()` was called in the iterator.
### 🙂 Expected behavior
The generated code should work like the original.
### Additional information about the issue
This only seems to be an issue when `yield*`ing values from another generator. The following works just fine:
```ts
///
(async () => {
async function* g() {
yield* [1, 2];
console.log('after');
}
const it = g()[Symbol.asyncIterator]();
console.log(await it.next());
await it.return?.();
})();
```
---
If you extract the generator into a constant and put the log in a while loop, the loop never exits:
```ts
///
(async () => {
async function* g() {
const x = (async function* () {
yield 1;
})();
for (let i = 0; i < 15; i++) {
yield* x;
console.log('after');
}
}
const it = g()[Symbol.asyncIterator]();
console.log(await it.next());
await it.return?.();
})();
```
```
{ value: 1, done: false }
after
after
after
after
after
...
```
---
If you inline the generator from the above example, the "after" is still logged, but the loop isn't run. Only if you extract the inline generator into a separate constant and only yield that within the loop instead, it keeps going.
```ts
///
(async () => {
async function* g() {
for (let i = 0; i < 15; i++) {
yield* (async function* () {
yield 1;
})();
console.log('after');
}
}
const it = g()[Symbol.asyncIterator]();
console.log(await it.next());
await it.return?.();
})();
```
```
{ value: 1, done: false }
after
```
---
The above does not apply to generators that only `yield*` themselves:
```ts
///
(async () => {
async function* id(x) {
yield* x;
}
async function* g() {
const x = (async function* () {
yield 1;
})();
for (let i = 0; i < 15; i++) {
yield* id(id(x));
console.log('after');
}
}
const it = g()[Symbol.asyncIterator]();
console.log(await it.next());
await it.return?.();
})();
```
```
{ value: 1, done: false }
after
after
after
after
after
...
```
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.