svelte
svelte copied to clipboard
Runes + async: add (or document) idiomatic way to work with async functions and runes
Describe the problem
In my Svelte 4 app, I keep all the logic inside stores and away from components. I found this approach to be more predictable and bug-free.
Here is an example of a derived store that updates whenever the user changes the settings, that relies on derived(v, (v, set) => { ... }). I don't understand how to rewrite this using runes.
Using $effect to synchronize state this way seems like an anti-pattern, not to mention the fact that since my state logic is outside the component tree, and I'd have to manage cleanup myself with $effect.root.
I've searched through the new docs, and I haven't encountered the word async there 😢.
Describe the proposed solution
Solid.js has an createResource: https://docs.solidjs.com/reference/basic-reactivity/create-resource , which is exactly this: a signal that updates with an async function whenever its deps update.
Importance
would make my life easier
I don't understand how to rewrite this using runes.
You don't have to. Runes are not a replacement for Svelte 4 stores, they work fine together. It would be nice to have async function support for derived stores, though.
Runes are a replacement, but stores are not being deprecated yet. See:
Runes are an additive feature, but they make a whole bunch of existing concepts obsolete: ...
- the store API and
$store prefix (while stores are no longer necessary, they are not being deprecated)
Using
$effectto synchronize state this way seems like an anti-pattern, not to mention the fact that since my state logic is outside the component tree, and I'd have to manage cleanup myself with$effect.root.
For asynchronous things, using $effect is, as far as I know, currently the expected way to do it.
If you have state outside the component lifecycle, that sounds more like an issue to me since it points towards global state which is rarely a good idea and an outright hazard if you are using server-side rendering.
Simple example:
export function getAsync(init, compute) {
let val = $state(init);
$effect(() => {
compute().then(result => {
if (result != getAsync.valueChanged)
val = result;
});
})
return () => val;
}
getAsync.valueChanged = Symbol('value changed');
let value = $state(1);
let derivedValue = $derived.by(getAsync(
undefined,
async () => {
// dependencies need to be read before first `await` happens
const currentValue = value;
const result = await fetchData(currentValue);
if (currentValue !== value)
return getAsync.valueChanged;
return result;
},
));
The reading of the dependencies before async code could be further enforced by having an API that splits the logic into reading and calculating. E.g. with a signature like this:
function getAsync(
init: T,
dependencies: () => D,
compute: (dependencies: D) => Promise<T>,
): () => T;
@brunnerh yes, I phrased my comment vaguely, but I meant that stores are not deprecated yet.
Furthermore, I noticed it's possible to return a Promise from $derived.by or pass an async function to it, thus avoiding $effect.
E.g.
async function asyncFetch(search: string) {
// do some fetching
}
let value = $state('') // will be used as an initial value
let derivedValue = $derived.by(() => {
return asyncFetch(value)
})
// or
let derivedValue = $derived.by(async () => {
const data = await asyncFetch(value)
// do some transformations on data
return transform(data)
})
{#await derivedValue}
<div>Loading...</div>
{:then value}
{value}
{/await}
This is similar to what is achieved with createResource in SolidJS.