Skip to content
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

Feat: $effect.async, $derived.async #14305

Open
Ocean-OS opened this issue Nov 14, 2024 · 8 comments
Open

Feat: $effect.async, $derived.async #14305

Ocean-OS opened this issue Nov 14, 2024 · 8 comments
Labels

Comments

@Ocean-OS
Copy link
Contributor

Describe the problem

There have been several discussions about the issues of runes and async, for example, #13916. Many apps depend on async code, and it can become difficult to migrate to Svelte 5 because of this.

Describe the proposed solution

It would be useful to have two asynchronous runes: $effect.async and $derived.async. These functions would be asynchronous counterparts to $effect and $derived.by, respectively. Here's an example:

let fetched = $derived.async(async()=>{
    let res = await fetch('./example.json');
    return await res.json(); 
});
$effect.async(async () =>{
    let asyncValue = await getAsyncValue();
    if(asyncValue === preferredAsyncValue) {
        runCallback(); 
    } 
}); 

You may notice that these functions do not require await to be called to use them, this would be because the await would be inserted at compile time:

let thing = $derived.async(asyncValue);
$inspect(thing); 
//turns into
let thing = await $.derived_async(asyncValue); 
$.inspect_async(async() =>[await $.get_async(thing)]); 

Importance

would make my life easier

@webJose
Copy link
Contributor

webJose commented Nov 14, 2024

And the expectation here would be for all reactive signals to account for effect re-run and derived value recalculation, even the ones read after awaiting? Otherwise it would be identical to the current $effect and $derived, in which case it would be simpler to just bring back the asynchronous typing for them (well, at least $effect once allowed async delegates).

@Ocean-OS
Copy link
Contributor Author

Ocean-OS commented Nov 14, 2024

The asynchronous effects and deriveds would run on a separate schedule from the regular effects and deriveds. This would have similar internal functionality to the regular scheduler, and could probably use the Scheduler API if needed, but that doesn't have full browser support.
This would be on a separate schedule to not slow down regular effects and deriveds (which would otherwise have to wait for asynchronous, and more time-consuming functions)

@paoloricciuti
Copy link
Member

This would be on a separate schedule to not slow down regular effects and deriveds (which would otherwise have to wait for asynchronous, and more time-consuming functions)

if you update the typings to accept an async function (btw i think we should not do such thing) but don't await the function when running the effect they would not wait for it.

@Ocean-OS
Copy link
Contributor Author

if you update the typings to accept an async function (btw i think we should not do such thing) but don't await the function when running the effect they would not wait for it.

Ah, I see. Is there a reason why you're against the type updates?

@webJose
Copy link
Contributor

webJose commented Nov 15, 2024

if you update the typings to accept an async function (btw i think we should not do such thing) but don't await the function when running the effect they would not wait for it.

I understand the core team's reasoning behind this, but it is counter-productive.

Let me explain.

Fact: Synchronous code inside effects is easier to understand and troubleshoot. It is simpler and more straightforward to follow in order to, for example, determine the reactive triggers.

Fact: Real world applications require tons of asynchronous operations triggered inside effects.

So, while the core team's reason behind disallowing async delegates for $effect is super understandable, reality forces us to do the React-like (which I hate) pattern:

$effect(() => {
  const x = async () => { ... };
  x();
});

// As opposed to allowing async:
$effect(async () => { ... });

Both are the same, work the same. So in the end, the prohibition of async on effect gains, what? In my eyes, the only gain of disallowing async is forcing users to the more verbose and unneeded syntax. Yes, users must also be taught that effect tracking only works up to the first await. I get it.

@paoloricciuti
Copy link
Member

Ah, I see. Is there a reason why you're against the type updates?

Because async code in effects and derived is very risky. Everything after an await is not tracked and it shouldn't be the simple thing to do. Generally you shouldn't ever use async in both of those but if you really really need you can do it but it's best that you think about it before doing it.

@trueadm
Copy link
Contributor

trueadm commented Nov 15, 2024

In the future we'll look into proper async primitives, as they will likely require some form of suspense to work correctly. As noted above, the reason why these can be problematic is because of a few reasons:

  • in effects, the destroy function is essential in many ways, if you make it async then it simply won't work
  • in effects/deriveds we need to track the reactive dependencies, this is only possible sync today in JavaScript because we lack something like AsyncContext
  • we can't just add awaits everywhere, as they too will need to be in an async context, so it ends up with us now working with promises instead of values – which are problematic to value equality but also things like error handling – if you throw an error then it might get swallowed by accident

Generally speaking, we want to avoid all these caveats entirely. If you have something async, you can call from it inside your synchronous effect. Watch this space too, as in the new year we hope to build out some powerful async primitives that will really help with these use-cases.

@paoloricciuti
Copy link
Member

in effects, the destroy function is essential in many ways, if you make it async then it simply won't work

Oh yeah I completely forgot about this too

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants