Fix: Don't wrap await inside @const async functions with save #16996
+90
−5
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Problem
Since Svelte 5.39.0, awaiting inside an async function assigned to a
@const
tag was broken. The component would not update/rerender when state changes occurred before the await, even though console logging showed the functions were being called correctly.Workaround: Moving the
await doSomething()
before theactiveTab = tab.id
fixed it, or definingswitchTab
in the script section instead of inline.Root Cause
The
is_reactive_expression
function inAwaitExpression.js
was returningtrue
for allawait
expressions when thein_derived
flag was set. When visiting@const
tag initialization expressions, the compiler setsin_derived: true
for the entire initialization, including any async functions defined within it.This caused
await
expressions inside those async functions to be incorrectly marked as "pickled awaits" and wrapped with$.save()
:The
$.save()
wrapper broke the reactivity chain, preventing the UI from updating.Solution
Modified the
is_reactive_expression
function to properly distinguish between:await
expressions inside regular functions (should NOT be pickled)await
expressions in reactive contexts like$derived
(SHOULD be pickled)When a function is found in the AST path, the fix checks if there's a reactive context (identified by nodes with
.metadata
or CallExpressions to reactive runes like$derived
,$effect
,$inspect
) between the function and theawait
. Only if such a reactive context exists is theawait
considered reactive.This preserves correct behavior for all cases:
{@const fn = async () => { await x() }}
- await not wrapped (fixed)$derived(await promise)
- await wrapped (preserved)async function() { $derived(await promise) }
- await wrapped (preserved)Testing
Fixes #15133
Original prompt
This section details on the original issue you should resolve
<issue_title>Regression: Awaiting inside an async function assigned to const broken since 5.39.0</issue_title>
<issue_description>### Describe the bug
Prior to 5.39.0, awaiting an async function inside a function assigned to a
@const
worked without issue. From 5.39.0 onward, even though console logging shows that both the parent and child functions are getting called with the right values, the actual component doesn't seem to be updating/rerendering, as if theactiveTab
assignment did nothing.Things that seem to change behavior:
await doSomething()
before theactiveTab = tab.id
fixes it (but in our case,doSomething
actually relies onactiveTab
being changed first, so this doesn't work for us)switchTab
a function in the script section (instead of inline) and then using<button onclick={() => switchTab(tab)}>
fixed it, this is what we have done to fix things on our endReproduction
Works on 5.38.10: https://svelte.dev/playground/9edd553009364fc7b6f6031a756b7acf?version=5.38.10
Broken from 5.39.0: https://svelte.dev/playground/9edd553009364fc7b6f6031a756b7acf?version=5.39.0
Broken in latest: https://svelte.dev/playground/9edd553009364fc7b6f6031a756b7acf?version=latest
(switch from Tab 1 to 2 to 3. Working version changes text all the time, broken version gets stuck after one click)
Logs
System Info
Severity
annoyance</issue_description>
<agent_instructions>This Svelte input
causes this output