Await a synchronous iterator's final value in for await

for await over a synchronous iterator held as any reads and awaits the value
of the result that finishes the loop, as the async-from-sync iterator does, so
a throwing value getter or a rejected promise there rejects the loop.
This commit is contained in:
Chris Tate
2026-10-20 18:49:56 -05:00
parent 4e5864bed4
commit 4a90c59cf3
3 changed files with 56 additions and 8 deletions
@@ -10807,8 +10807,8 @@ function lowerForOfDyn(
}
/** One step of `for await` over a checked-dynamic iterator: await next()'s
* result, read done before value, and await the value of a synchronous
* iterator, as the async-from-sync iterator does. */
* result, read done before value, and await each value of a synchronous
* iterator, including its final one, as the async-from-sync iterator does. */
function asyncIteratorValue(
lowerer: Lowerer,
iterator: IrExpr,
@@ -10866,14 +10866,29 @@ function asyncIteratorValue(
loc,
},
],
// A synchronous iterator's final value is still read and awaited, so a
// throwing getter or a rejected promise there rejects the loop.
result: {
kind: "ternary",
cond: varRef(done.id, BOOL, loc),
then: dynUndefinedExpr(loc),
cond: sync,
then: {
kind: "ternary",
cond: varRef(done.id, BOOL, loc),
then: {
kind: "seqExpr",
stmts: [{ kind: "exprStmt", expr: awaitDyn(get("value")), loc }],
result: dynUndefinedExpr(loc),
type: DYN,
loc,
},
else_: awaitDyn(get("value")),
type: DYN,
loc,
},
else_: {
kind: "ternary",
cond: sync,
then: awaitDyn(get("value")),
cond: varRef(done.id, BOOL, loc),
then: dynUndefinedExpr(loc),
else_: get("value"),
type: DYN,
loc,
+34 -1
View File
@@ -1,6 +1,25 @@
// for await and async yield* over values held as any: a Symbol.asyncIterator
// method that returns a compiled generator, a synchronous delegate whose
// final value is a promise, and return() results that are not objects.
// final value is a promise, a synchronous iterator whose final value throws
// or rejects, and return() results that are not objects.
const settle: any = (value: unknown, fails: boolean) =>
new Promise((resolve, reject) => (fails ? reject(new Error(String(value))) : resolve(value)));
function finishing(last: () => any): any {
return {
[Symbol.iterator]() {
let step = 0;
return { next: () => (step++ < 1 ? { value: 1, done: false } : last()) };
},
};
}
async function loop(label: string, source: any): Promise<void> {
try {
for await (const value of source) console.log(label, "value", value);
console.log(label, "completed");
} catch (error) {
console.log(label, "caught", (error as Error).message);
}
}
async function main(): Promise<void> {
const generated: any = {
async *[Symbol.asyncIterator]() {
@@ -56,5 +75,19 @@ async function main(): Promise<void> {
} catch (error) {
console.log("return", (error as Error).name, (error as Error).message);
}
await loop("getter", finishing(() => ({ done: true, get value(): number { throw new Error("value getter"); } })));
await loop("rejected", finishing(() => ({ value: settle("rejected final", true), done: true })));
await loop("resolved", finishing(() => ({ value: settle(9, false), done: true })));
await loop("rejected step", finishing(() => ({ value: settle("rejected step", true), done: false })));
async function* finalRejects(): AsyncGenerator<unknown, void> {
const result = yield* finishing(() => ({ value: settle("delegate final", true), done: true }));
console.log("delegate completed", result);
}
try {
for await (const value of finalRejects()) console.log("delegate value", value);
} catch (error) {
console.log("delegate caught", (error as Error).message);
}
}
main();
@@ -1 +1 @@
{ "plain": 3200, "san": 3800 }
{ "plain": 3200, "san": 4100 }