8000 Await expressions · Issue #1857 · sveltejs/svelte · GitHub
[go: up one dir, main page]

Skip to content

Await expressions #1857

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

Open
Rich-Harris opened this issue Nov 20, 2018 · 11 comments · May be fixed by #15844
Open

Await expressions #1857

Rich-Harris opened this issue Nov 20, 2018 · 11 comments · May be fixed by #15844
Milestone

Comments

@Rich-Harris
Copy link
Member
Rich-Harris commented Nov 20, 2018

Just want to capture a thought I had the other day: it might be neat to have inline await expressions in templates. We already have {#await ...} blocks but they're overkill in some situations — you have to declare a name for the resolved value, which you might only be using once, and you might not need to worry about error states depending on what you're doing.

Imagine something like this (v3 syntax):

<script>
  async function fibonacci(n) {
    return await fibonacciWorker.calculate(n);
  }
</script>

<input type=number bind:value={n}>
<p>The {n}th Fibonacci number is {await fibonacci(n)}</p>

It would integrate with Suspense, so it'd be convenient for doing this sort of thing (where loadImage resolves to its input, but only after ensuring that the image is loaded):

<script>
  import loadImage from './utils.js';
</script>

{#each thumbnails as thumbnail}
  <img alt={thumbnail.description} src="{await loadImage(thumbnail.src)}">
{/each}

Of course, you'd need some way to have placeholder content, for situations where you're not using Suspense. Maybe this?

<p>The {n}th Fibonacci number is {await fibonacci(n) or '...'}</p>
@nsivertsen
Copy link

This would make a lot of things easier, especially around lazy-loading.

One question: This would only ever run on the client, and not in SSR, correct?

@Rich-Harris
Copy link
Member Author

With the current SSR process, yeah. I'd like to look into ways we could accommodate async/streaming SSR in v3 though

@tivac
Copy link
Contributor
tivac commented Nov 20, 2018

@Rich-Harris Your Suspense link is just a link back to this same issue. 🤔

@Rich-Harris
Copy link
Member Author

D'oh. Fixed

@RedHatter
Copy link
Contributor

I really like this. I tend to work with promises a lot and this would make most of my use cases a lot cleaner and easier.

@frederikhors

This comment has been minimized.

@ansarizafar

This comment has been minimized.

@rohanrichards
Copy link

I was just looking over the docs today for inline awaits. I am predominantly an Angular developer and am used to this syntax in the templates (html)

<div>{{promiseResult | await}}</div>

I'm currently writing some Svelte UI code that right now looks like this:

{#await streamlabsInterface.getSourceNameFromId(node.sourceId) then name}{name}{/await}

Ignoring the over-verbosity of my class/method names, inline await would make this much nicer to work with, are pipes a possibility at all in Svelte?

@dummdidumm
Copy link
Member

O more general solution would be to leverage the pipe syntax like rohan suggests. This would make it possible to provide other features besides await and users would be able to provide custom ones themselves.

@Prinzhorn
Copy link
Contributor
Prinzhorn commented Jul 12, 2021

Moving forward I'd rather see {#await} being removed than adding more {#await}. But that's just from my experience and I'm sure there are use-cases for it.

When I started with Svelte I briefly used {#await} but quickly realized it is way too limited. AbortControllers are now supported in a variety of APIs and libraries. Just "ignoring" a Promise result if it is not longer needed is an antipattern in my opinion. In your original example you will end up with multiple Fibonacci workers eating away your CPU because you never stop them even if the result is no longer needed. If the user quickly clicks the up/down arrow on your [number] input you keep spawning workers. Unless of course fibonacciWorker.calculate would be mutually exclusive and handles that, but then you couldn't have two of them on the same page.

Another limitation is that you are forced into the syntax of the {#await} block. What I mean by that is that for example you can't add a loading class to a parent. You can only render stuff for the loading state in the given block. Nowhere else.

I personally abstract everything away into stores. Stores are amazing. With everything I mean things like fetch, Worker or WebSocket. For fetch the store data is an object with loading, error and data (with a default) properties that you can use or not. Here's the Fibonacci example using one of the patterns I'm using a lot:

import { readable } from 'svelte/store';

export const fibonacci = function (n, initialData) {
  return readable(
    {
      loading: true,
      error: null,
      data: initialData,
    },
    (set) => {
      let controller = new AbortController();

      (async () => {
        try {
          let result = await fibonacciWorker.calculate(n, {
            signal: controller.signal
          });

          set({
            loading: false,
            error: null,
            data: result,
          });
        } catch (err) {
          // Ignore AbortErrors, they're not unexpected but a feature.
          // In case of abortion we just keep the loading state because another request is on its way anyway.
          if (err.name !== 'AbortError') {
            set({
              loading: false,
              error: err,
              data: initialData,
            });
          }
        }
      })();

      return () => {
        controller.abort();
      };
    }
  );
};
<script>
  import { fibonacci } from './math.js';
  $: result = fibonacci(n, 0);
</script>

<input type=number bind:value={n}>
<p>The {n}th Fibonacci number is {$result.data}</p>

{#if $result.loading}
  <p>Show a spinner, add class or whatever you need.</p>
  <p>You are not limited to the syntax of an #await block. You are free to do whatever you want.</p>
{/if}

Like I said, that's just from my experience. Maybe Svelte can either offer a way to turn promises into stores or advocate this in user land. I'm using this with great success. No need to debounce fetch (terrible UX), just fire them away. Aborting them will also cancel the server operations (e.g. in Go you can use https://golang.org/pkg/context/).

Once you've written the imperative library/util code once, your components are super slim and completely reactive/declarative. Wow.

Edit: For people that want the existing semantics (without aborting) but with a store API maybe Svelte could add this:

<script>
  import { fromPromise } from 'svelte/store';

  $: result = fromPromise(fibonacciWorker.calculate(n), 0);
</script>

<input type=number bind:value={n}>
<p>The {n}th Fibonacci number is {$result.data}</p>

Yes I love stores.

@Akolyte01
Copy link

At Square we've followed a similar route as @Prinzhorn in the abundant usage of stores to solve more complicated versions of this problem. We have developed some patterns in the @square/svelte-store package with custom stores that help drastically reduce the amount of boilerplate required to accomplish similar tasks.

I've spun up a suite of examples here: https://codesandbox.io/s/solving-async-problems-with-stores-kr712b

Taking this approach lets you use both #await and state-based conditional rendering, depending on the exact use case. Having access to a promise becomes very useful when you start dealing with more complicated flows of asynchronous data, such as when you want to fetch asynchronous data based on other asynchronous data

However if you don't need this level of control all of this is much heavier than the proposed inline {await}

Something that might additionally be helpful is allowing users to await promises inside reactive statements. As is you need to do something like this:

$: ((input) => {
       fibonacci = await fibonacciWorker.calculate(input);
    })(n)   

But it would be nice to be able to just do this!

$: { fibonacci = await fibonacciWorker.calculate(n); }

@Rich-Harris Rich-Harris added this to the 5.x milestone Apr 1, 2024
@Rich-Harris Rich-Harris linked a pull request May 20, 2025 that will close this issue
6 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

0