Read the final result's value only for sync pipeline sources

for await reads the value of a done result only through the
async-from-sync wrapper; an async iterator's done result is not read.
The source's generator read it for both, so an async iterator whose
done result has a throwing value getter rejected the pipeline. It now
reads and awaits the final value only when the iterator is sync.
This commit is contained in:
Chris Tate
2026-10-24 17:59:56 -05:00
parent 6e4bfb3c91
commit fcc81c034c
2 changed files with 48 additions and 5 deletions
@@ -4085,14 +4085,23 @@ function lowerDynIterableSource(
type: BOOL,
loc,
},
// the final value is awaited too: a rejection rejects the
// pump, and the finished iterator is not closed
// a sync iterator's final value is awaited too: a rejection
// rejects the pump, and the finished iterator is not closed.
// An async iterator's final value is not read.
then: [
{ kind: "assign", localId: valueId, value: get(step(), "value"), loc },
{
kind: "if",
cond: awaitsValue(),
then: [{ kind: "exprStmt", expr: awaitDyn(value()), loc }],
cond: sync(),
then: [
{ kind: "assign", localId: valueId, value: get(step(), "value"), loc },
{
kind: "if",
cond: awaitsValue(),
then: [{ kind: "exprStmt", expr: awaitDyn(value()), loc }],
else_: null,
loc,
},
],
else_: null,
loc,
},
@@ -135,3 +135,37 @@ for (const [label, source] of [["finalRejects", finalRejects], ["finalResolves",
console.log(label, "rejected", err.message);
}
}
// the final result's value is read for a sync iterator only: an async
// iterator's done result is not read, so a throwing value getter there is
// never called
function finalResult(label, i) {
if (i === 1) return { value: `${label}1`, done: false };
return {
done: true,
get value() {
throw new Error(`${label} final value read`);
},
};
}
function asyncFinal() {
let i = 0;
return { [Symbol.asyncIterator]() { return { async next() { i++; return finalResult("async", i); } }; } };
}
function syncFinal() {
let i = 0;
return { [Symbol.iterator]() { return { next() { i++; return finalResult("sync", i); } }; } };
}
async function runFinal(label, source) {
try {
await pipelineP(
pick(source),
new Writable({ objectMode: true, write(c, _e, cb) { console.log(label, "write", c); cb(); } }),
);
console.log(label, "resolved");
} catch (err) {
console.log(label, "rejected", err.message);
}
}
await runFinal("async final", asyncFinal());
await runFinal("sync final", syncFinal());