Skip to content

Svelte Suspense #1736

Closed
Closed
@Rich-Harris

Description

@Rich-Harris

A few people have asked whether Svelte will ever support something like React Suspense (if you're not familiar with the idea, Dan did some helpful tweets tonight).

With Svelte's current approach Suspense is impossible. I haven't been too concerned about that — it's not generally a problem in Sapper apps because of preload, for example, and I have some concerns about the mechanism by which it's implemented (i.e. throwing promises, which seems likely to create tricky-to-identify-or-reason-about situations where things that should be happening in parallel end up happening serially) — but it is a cool idea, and I get a lot of pleasure out of saying 'of course we have [x] feature' to sceptical React devs.

Consider an app like this:

<!-- App.html -->
<label>
  <input type=checkbox bind:checked=visible> visible
</label>

{#if visible}
  <svelte:placeholder this={Loading}>
    <Thing/>
  </svelte:placeholder>
{/if}
<!-- Thing.html -->
<h2>This header should not be visible until <code>bar</code> is ready</h2>

{#await foo then bar}
  <p>{bar}</p>
{/await}

<script>
  const wait = ms => new Promise(f => setTimeout(f, ms));
	
  export default {
    data() {
      return {
        foo: wait(1000).then(() => 42)
      };
    }
  };
</script>

It'd be great if, on toggling the checkbox, Svelte waited for the foo promise to resolve before updating anything inside the <svelte:placeholder>. But the code Svelte generates makes this impossible, because there's no intermediate virtual DOM — state changes are applied immediately to the real DOM:

// snippet from compiled Hello World example
if (changed.name) {
  setData(text_1, ctx.name);
}

If we changed that to something along these lines...

if (changed.name) {
  ops.push([setData, text_1, ctx.name]);
}

...then we could decide whether or not to apply those operations based on whether there were any unresolved async data dependencies. (I'm glossing over a lot of detail here but that's the basic concept.)

In React, async data dependencies are declared by throwing a promise, as mentioned above. In Svelte, we could use await blocks, which has the crucial advantage that you don't need to jump through somewhat confusing hoops like this:

// without the preload, we won't start fetching `bar` until after
// `foo` has resolved, because `resource.read` will initially throw
resource.preload('foo');
resource.preload('bar');

const foo = await resource.read('foo');
const bar = await resource.read('bar');

The downside is that it involves generating slightly more code, and doing slightly more work, than if the operations are applied immediately. I'm curious about whether people think the trade-off would be worth it.

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