You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Inside an async function, a let/const declared in a loop body and captured by a closure loses its per-iteration binding: every closure sees the last iteration's value. The same code in a non-async function is correct.
Capturing the loop variable directly (fns.push(() => console.log(i))) is equally affected — node prints 0..8 (per-iteration let binding), perry prints 9 nine times (the single shared cell, read after the loop has exited).
Notes
Drop the await (making main a plain function, or an async function with no await) and the output is correct — so the trigger is the async-to-generator transform, which hoists every body local into a single boxed mutable capture on the step closure. That one cell is shared across iterations, collapsing the per-iteration binding that let/const require.
Likely affects any for/while body that creates closures inside an async function — event-handler registration loops, per-item callbacks, setTimeout in a loop.
Summary
Inside an
asyncfunction, alet/constdeclared in a loop body and captured by a closure loses its per-iteration binding: every closure sees the last iteration's value. The same code in a non-async function is correct.Silent wrong answer — no throw, exit 0.
Repro
j = 0,j = 1, …j = 8mainj = 8× 9Capturing the loop variable directly (
fns.push(() => console.log(i))) is equally affected — node prints0..8(per-iterationletbinding), perry prints9nine times (the single shared cell, read after the loop has exited).Notes
await(makingmaina plain function, or anasyncfunction with noawait) and the output is correct — so the trigger is the async-to-generator transform, which hoists every body local into a single boxed mutable capture on the step closure. That one cell is shared across iterations, collapsing the per-iteration binding thatlet/constrequire.await Promise.all([...])never resumes when inputs settle via executor-captured resolvers — program silently exits 0 #6328 / PR fix(runtime): numeric-key computed calls must read the element, not dispatch by name (#6328) #6344 (numeric-key computed calls). It reproduces on unpatchedmainvia a call shape that already worked (const f = fns[i]; f(i)), and it still reproduces with fix(runtime): numeric-key computed calls must read the element, not dispatch by name (#6328) #6344 applied. Filing separately so the two fixes don't get tangled: fix(runtime): numeric-key computed calls must read the element, not dispatch by name (#6328) #6344 is a runtime fix, this one belongs inperry-transform's async lowering.for/whilebody that creates closures inside an async function — event-handler registration loops, per-item callbacks,setTimeoutin a loop.