-
-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Open
Description
Describe the bug
Related discussion: #15845
Using an {#await} block inside an {#each} block that iterates over a legacy readable store causes the browser to freeze/crash due to a microtask loop.
It seems that {#await} is re-triggering the promise creation function infinitely because the function returns a new Promise reference every time, and the reactivity system re-evaluates it aggressively.
Reproduction
This code snippet freezes the browser:
<script module lang="ts">
import { readable, type Readable } from "svelte/store";
interface Entity {
id: string;
}
export function f(): Readable<Entity[]> {
return readable([{ id: crypto.randomUUID() }], (_set, update) => {
const timer = setInterval(() => {
update((x) => [...x, { id: crypto.randomUUID() }]);
}, 1000);
return () => clearInterval(timer);
});
}
export async function query(entity: Entity): Promise<number[]> {
return Promise.resolve([1, 2, 3]);
}
</script>
<script lang="ts">
const entities = f();
</script>
<ul>
{#each $entities as entity (entity.id)}
<li>{entity}</li>
<ul>
{#each await query(entity) as value}
<li>{value}</li>
{/each}
</ul>
{/each}
</ul>But this works (using {#await}):
<script module lang="ts">
import { readable, type Readable } from "svelte/store";
interface Entity {
id: string;
}
export function f(): Readable<Entity[]> {
return readable([{ id: crypto.randomUUID() }], (_set, update) => {
const timer = setInterval(() => {
update((x) => [...x, { id: crypto.randomUUID() }]);
}, 1000);
return () => clearInterval(timer);
});
}
export async function query(entity: Entity): Promise<number[]> {
return Promise.resolve([1, 2, 3]);
}
</script>
<script lang="ts">
const entities = f();
</script>
<ul>
{#each $entities as entity (entity.id)}
<li>{entity}</li>
<ul>
{#await query(entity) then result}
{#each result as value}
<li>{value}</li>
{/each}
{/await}
</ul>
{/each}
</ul>this works too (using "svelte/reactivity"):
<script module lang="ts">
import { createSubscriber } from "svelte/reactivity";
interface Entity {
id: string;
}
class Foo {
private value: Entity[];
private sub: () => void;
constructor() {
this.value = [{ id: crypto.randomUUID() }];
this.sub = createSubscriber((update) => {
const timer = setInterval(() => {
this.value = [...this.value, { id: crypto.randomUUID() }];
update();
}, 1000);
return () => clearTimeout(timer);
});
}
get result(): Entity[] {
this.sub();
return this.value;
}
}
export async function query(entity: Entity): Promise<number[]> {
return Promise.resolve([1, 2, 3]);
}
</script>
<script lang="ts">
const entities = new Foo();
</script>
<ul>
{#each entities.result as entity (entity.id)}
<li>{entity}</li>
<ul>
{#each await query(entity) as value (value)}
<li>{value}</li>
{/each}
</ul>
{/each}
</ul>this works too (using primitive instead of object)
<script module lang="ts">
import { readable, type Readable } from "svelte/store";
type Entity = string;
function f(): Readable<Entity[]> {
return readable([crypto.randomUUID()], (_set, update) => {
const timer = setInterval(() => {
update((x) => [...x, crypto.randomUUID()]);
}, 1000);
return () => clearInterval(timer);
});
}
export async function query(entity: Entity): Promise<number[]> {
return Promise.resolve([1, 2, 3]);
}
</script>
<script lang="ts">
const entities = f();
</script>
<ul>
{#each $entities as entity}
<li>{entity}</li>
<ul>
{#each await query(entity) as value (value)}
<li>{value}</li>
{/each}
</ul>
{/each}
</ul>System Info
System:
OS: macOS 26.2
CPU: (12) arm64 Apple M3 Pro
Memory: 1.67 GB / 36.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 25.2.1 - /opt/homebrew/bin/node
npm: 11.6.2 - /opt/homebrew/bin/npm
pnpm: 10.28.0 - /opt/homebrew/bin/pnpm
bun: 1.3.6 - /opt/homebrew/bin/bun
Browsers:
Chrome: 143.0.7499.193
Safari: 26.2
npmPackages:
svelte: ^5.43.8 => 5.46.3Severity
annoyance
Metadata
Metadata
Assignees
Labels
No labels