Skip to content

Svelte Suspense #1736

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Rich-Harris opened this issue Sep 13, 2018 · 8 comments
Closed

Svelte Suspense #1736

Rich-Harris opened this issue Sep 13, 2018 · 8 comments

Comments

@Rich-Harris
Copy link
Member

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.

@stalkerg
Copy link
Contributor

hmmm... in any case, we can just move h2 into await block.
Also, this behavior will be implicit, I mean it should be the more clear syntax.

But I like next feature:

As a bonus, in concurrent mode we can skip showing the spinner altogether if it loads fast enough.

really help me

@TehShrike
Copy link
Member

I hadn't heard of React Suspense before, so I'm still absorbing this - is the idea that you have the capability to say "display this other component as a placeholder until all await blocks are resolved in the actual target component"?


If I understand the goal, then this seems like something that should go in Sapper rather than Svelte itself. I feel like these kinds of more-than-a-few-ms-loading-time state changes should be serialized in the url, and be contained in a route/component nesting level.

That said, Svelte does have {#await}, so maybe it would at least make sense to expose a areAllAwaitBlocksResolved() method or something.

@Rich-Harris
Copy link
Member Author

hmmm... in any case, we can just move h2 into await block.

The point is that you could have multiple async dependencies at arbitrary depths in your tree:

<h2>Header that should not appear yet</h2>

<!-- Thing contains a component, that contains two components that contain async deps -->
<Thing/>

Worth watching the Iceland talk to understand the motivations more clearly.

If I understand the goal, then this seems like something that should go in Sapper rather than Svelte itself.

Not possible — it's something the compiler needs to be aware of. Sapper is, at heart, just a thing that constructs a tree of data containing components each time you navigate. (I haven't yet thought about the interplay between this and preload, or what SSR looks like in this world.) Moreover I don't think it's desirable to have Sapper-specific features; even if it's the 'official' app framework for Svelte, we should avoid lock-in as far as possible.

I feel like these kinds of more-than-a-few-ms-loading-time state changes should be serialized in the url

There are lots of counter-examples — for example 'this modal is open' probably isn't something you want to serialize in the URL, in most cases. And an async dependency could be as trivial as 'wait until this image has loaded, so we know the dimensions before we render these new elements, to avoid the layout jumping'.

An areAllAwaitBlocksResolved function would still rely on changing setData(node, data) to ops.push([setData, node, data]) in order to be useful, I think.

@brucou
Copy link

brucou commented Apr 6, 2019

Can't suspense be implemented with Svelte with state machines? Wait x seconds for a request, if arrived, display that, if not display this -> that is a state machine. Some behaviour which comes up all the time could be turned into predefined state machines with the stuff to render as children with slot corresponding to the state of the state machine (loaded, waiting, etc.).

Just a quick idea, not sure if it makes sense but that would be standard Svelte templating with no additional syntax -> no debugging on svelte/sapper side!

@Ryuno-Ki
Copy link

If not with state machines, take a look into statecharts (e.g. xstate).

@brucou
Copy link

brucou commented Jun 18, 2019

@Ryuno-Ki I have my own state machine library which i used for about three years now, and deals with these cases reasonably nicely. I am in the process of integrating it with Svelte which is why I came to think about using it also for suspense-like features. Once that is done, I will take a shot at it. I may be naive but for what I understood suspense is just about adding a conditional filter or proxy between a rendering request and a rendering execution. That should be able by intercepting the rendering requests (e.g. state changes as one means the other) and a simple state machine with three or four states should suffice. I don't think there is any need for statecharts here. But anyways, more news when I get to it.

@brucou
Copy link

brucou commented Jun 28, 2019

I finalized a proof of concept. This example was useful and outlined some benefits and some difficulty of Svelte's compiler approach. I will address this in a new issue.

@antony
Copy link
Member

antony commented Apr 9, 2020

Closing in favour of new issue linked by @brucou - and potentially an RFC.

@antony antony closed this as completed Apr 9, 2020
@antony antony removed the awaiting submitter needs a reproduction, or clarification label Apr 9, 2020
@sveltejs sveltejs locked as resolved and limited conversation to collaborators Apr 9, 2020
@antony antony added the stale label Apr 9, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants