Summary
await Promise.all([...]) never resumes when the input promises are settled through executor-captured resolvers. The program does not hang — it silently exits 0 having printed nothing, which is worse: no crash, no diagnostic, just a missing result.
The .then() form of the same code works. Confirmed on clean origin/main (0960415ad).
Repro
async function main() {
const resolvers: ((v: number) => void)[] = [];
const ps = Array.from({ length: 100 }, () => new Promise<number>((res) => { resolvers.push(res); }));
const all = Promise.all(ps);
for (let i = 0; i < 100; i++) resolvers[i](i);
const r = await all; // never resumes
console.log("awaited", r.length);
}
main();
|
output |
exit |
| node 26 |
awaited 100 |
0 |
perry main |
(nothing) |
0 |
Notes
- The resolvers are captured out of the
new Promise executor and invoked synchronously, after Promise.all(ps) has already been constructed.
- Replacing
await all with all.then(r => console.log("awaited", r.length)) works, so the combinator itself settles — it is the await resumption that is lost.
- A variant where the resolvers are invoked later from a
setTimeout does work, so the synchronous-settle-after-construction ordering appears to be load-bearing.
Surfaced while working on #6084 (PR #6327, promise side-table O(N^2)); that PR does not touch this and the bug reproduces identically without it. Filed separately so the perf work isn't blocked.
Silent wrong answer with a zero exit code — an async entry point can evaporate and CI would call it a pass.
Summary
await Promise.all([...])never resumes when the input promises are settled through executor-captured resolvers. The program does not hang — it silently exits 0 having printed nothing, which is worse: no crash, no diagnostic, just a missing result.The
.then()form of the same code works. Confirmed on cleanorigin/main(0960415ad).Repro
awaited 100mainNotes
new Promiseexecutor and invoked synchronously, afterPromise.all(ps)has already been constructed.await allwithall.then(r => console.log("awaited", r.length))works, so the combinator itself settles — it is theawaitresumption that is lost.setTimeoutdoes work, so the synchronous-settle-after-construction ordering appears to be load-bearing.Surfaced while working on #6084 (PR #6327, promise side-table O(N^2)); that PR does not touch this and the bug reproduces identically without it. Filed separately so the perf work isn't blocked.
Silent wrong answer with a zero exit code — an
asyncentry point can evaporate and CI would call it a pass.