Skip to content

"svelte/store" with Asynchronous Svelte freezes the browser #17471

@MioYiSama

Description

@MioYiSama

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.3

Severity

annoyance

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions