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
Async reactive declarations #2118
Comments
I'm not sure what this would entail. You can still |
For anyone who's stumbled across this issue in search for asynchronous reactive declarations (like me) — here's two ways to achieve them in Svelte: Reactive Statementslet package_name = 'svelte';
let download_count = 0;
$: fetch('https://api.npmjs.org/downloads/point/last-week/' + package_name)
.then(response => response.json())
.then(data => download_count = data.downloads || 0);
// Updating `package_name` will asynchronously update `download_count` Pros & Cons
More Information
Derived Storesimport { writable, derived } from 'svelte/store';
const package_name = writable('svelte');
const download_count = derived(
package_name,
($package_name, set) => {
fetch('https://api.npmjs.org/downloads/point/last-week/' + $package_name)
.then(response => response.json())
.then(data => set(data.downloads));
return () => {
// We override the `set` function to eliminate race conditions
// This does *not* abort running fetch() requests, it only prevents
// them from overriding the store.
// To learn about canceling fetch requests, search the internet for `AbortController`
set = () => {}
}
}
);
// Updating `$package_name` will asynchronously update `$download_count` Pros & ConsPretty much the opposite of the "reactive statements" approach:
More Information
@Conduitry I think this topic may deserve its own section in the docs. All the ways to achieve this are properly documented, but I'd think quite some people are actually searching the docs for the "async reactive declarations" keyword and currently not finding anything. |
@loilo's example is great, but is still missing progress and errors. Progress: consider a remote service that takes 5 seconds to respond. The download number will be stale until the derived store calls Errors: What if the server responds 500 internal server error? The example keeps showing the old number. You can construct both by setting other properties inside the Promise callbacks, but we have Me personally, I want to submit a form field to the server when it's changed (with debounce & idle), and notify user with messages like "Saving..." and display errors. If I could easily derive a "status" prop from the form field value, I'd be happy. It seems possible but it's only simple if you neglect aspects of it. |
@tv42 In my experience, the type of async reactive declarations requested in this issue explicitely asks for keeping stale data until new data is available, mostly to avoid unwanted flashes of content until that new data is available. If you don't need the stale data and just want to use promises for progress tracking, your code would be as simple as this (and even safe from race-conditions!): let package_name = 'svelte';
$: download_count = fetch('https://api.npmjs.org/downloads/point/last-week/' + package_name)
.then(response => response.json())
.then(data => download_count = data.downloads || 0 }) That said, while your needs for progress & error handling are very valid, to me they don't sound like something a runtime-frowning framework like Svelte should handle. Your points could also be funneled into my previous comment's examples relatively easily (additional boolean helper variable/store for progress tracking and direct error propagation for failure): With reactive statements: let package_name = 'svelte';
let downloading = false;
let download_count = 0;
$: {
downloading = true;
fetch('https://api.npmjs.org/downloads/point/last-week/' + package_name)
.then(response => response.json())
.then(data => download_count = data.downloads || 0 })
.catch(error => download_count = error) // check for error in template
.then(() => downloading = false);
}; A little more code shifting is required with derived stores: import { writable, derived } from 'svelte/store';
const package_name = writable('svelte');
const downloading = writable(false);
// ^-- could also use a `readable` store to prevent manipulation
// from the outside, but let's keep it writable for brevity
const download_count = derived(
package_name,
($package_name, set) => {
// Flag to keep track of possible newer derivations
let is_latest = true;
fetch('https://api.npmjs.org/downloads/point/last-week/' + $package_name)
.then(response => response.json())
.then(data => is_latest && set(data.downloads))
.catch(error => is_latest && set(error)) // check for error in template
.then(data => is_latest && downloading.set(false));
// Mark this context as superseded on cleanup
return () => is_latest = false
}
); |
I ended up writing a custom store that "buffers" sets for both a small time interval and ensuring only one async action is in flight (and triggering an extra round of async processing if a set was seen after the last async action was launched). Usage example:
Reading Update: ... which sort of sucks because the |
What I'm wondering is why it doesn't work to do something like this:
Recently I wrote a component with the pattern above, where It seemed to work quite well, but then on further inspection I noticed that sometimes -- seemingly randomly -- the cards wouldn't update. Everything else was updating -- Once I replaced it with the |
Another way to do the same is: import { getItems } from './getItems';
let filtered;
$: (async() => filtered = await getItems())(); So, I don't think we should have aditional processing of this. |
@PaulMaly Fantastic, that's exactly what I was looking for! Thank you! |
I think this can be closed. As noted above, you can already do this with existing syntax. |
@PaulMaly Tried your solution: $: (async() => {
rows = await sourceDefinition.fetch(sortBy, sortAsc);
console.log('DATA', rows);
}); This is never called. I need this statement to be called when Note: An example on the official documentation/examples would be a nice addition. |
I ended up with this workaround: const update = async (sortBy, sortAsc) => {
rows = await sourceDefinition.fetch(sortBy, sortAsc);
console.log('DATA', rows);
}
$: update(sortBy, sortAsc); Works great, but I have to declare an additional function to pass my arguments. I'm quite uncomfortable with this solution, can this be done with a better way? 🤔 |
@soullivaneuh You just need to fix your code exactly as I wrote before. Pay attention, this is not a regular function, it's IIFE. Also, you can find here even more examples of |
I finally found a simpler and more reliable solution: <script>
$: {
rows = sourceDefinition.fetch(sortBy, sortAsc, search, query);
console.log('Updated rows:', rows);
};
</script>
{#await rows}
Loading...
{:then resolvedRows}
<DataTable {columns} rows={resolvedRows} />
{:catch error}
<p class="text-red-500">{error.message}</p>
{/await} Thanks for the feedback @PaulMaly! |
@soullivaneuh Your solution is a very basic. The case above is more complex because using your solution you can't manipulate with fetched data outside of template and even outside |
Apologies for the necrobump, but out of curiosity, is this async (which might be an ajax call to somewhere) be fired every time some other piece of state updates? |
@trev-dev yep, every time when its dependencies will changed. |
It looks more declarative than Wouldn't it be great if this pattern could be simply written as: let filtered = await getItems(); Basically, this statement is currently not allowed as |
Here is another approach in case you need to load data asynchronously and then apply e.g. client-side filtering: {#await rows}
Loading...
{:then resolvedRows}
{@const filteredRows = clusters.filter((item) => matches(item, filter))} // apply client side filtering once and store in `filteredRows` const
<DataTable {columns} rows={filteredRows} />
{:catch error}
<p class="text-red-500">{error.message}</p>
{/await} |
Async reactive declaration that returns resolved promise would be a nice addition and sufficiently important If you are using a function form a third party module which returns a promise. This feature has also been discussed in the Support channel recently.
The text was updated successfully, but these errors were encountered: