I tried to use async #each
loop in svelte and found it it runs synchronously no matter what, using async await function like this:
{#each items as item (item.id)}
{#await render(item) then source}
<Canvas {source} />
{:catch}
<p>error</p>
{/await}
{/each}
and found it there is no way to use async component in svelte cause onmount can't be used asynchronously like this:
{#each items as item (item.id)}
<Canvas {item} />
{/each}
is there any workaround this problem?
Based on your comment it sounds like you are having trouble updating elements in the array using async. Here is an example initializing in onMount
using an async function, and then rerendering using the immutable
flag to only update those elements that have changed. This example puts all the promise handling in the script rather than in the jsx.
// App.svelte
<svelte:options immutable={false} />
<script>
import Item from "./Item.svelte";
import { onMount } from "svelte";
let array;
const sleep = async (ms) => await new Promise((r) => setTimeout(r, ms));
async function render(id) {
await sleep(800);
array = array.map((item) => {
if (item.id === id) {
return {
...item,
render: item.render + 1,
};
}
return item;
});
}
onMount(() => {
async function init() {
array = await Promise.all(
[...Array.from({ length: 5 }).keys()].map((i) =>
Promise.resolve({
id: i + 1,
name: `item_${i + 1}`,
render: 0,
})
)
);
}
init();
return () => console.log("destroyed");
});
</script>
{#if array && array.length}
{#each array as item (item.id)}
<Item {item} on:click={() => render(item.id)} />
{/each}
{/if}
// ./Item.svelte
<svelte:options immutable={true} />
<script>
import { afterUpdate } from "svelte";
export let item;
</script>
<div>
<p>
{item.name} [render: {item.render}]
</p>
<button on:click>render</button>
</div>
But you could also incorporate an {#await}
block to handle an array of promises in the jsx. (It uses the same Item
component as above)
// App.svelte
<svelte:options immutable={false} />
<script>
import Item from "./Item.svelte";
import { onMount } from "svelte";
let array;
async function render(id) {
array = await Promise.all(
array.map(async (promiseItem) => {
if (promiseItem.id === id) {
const _item = await promiseItem.item;
return {
id: promiseItem.id,
item: new Promise((r) =>
setTimeout(() => r({ ..._item, render: _item.render + 1,}), 800)
),
};
}
return promiseItem;
})
);
}
onMount(() => {
async function init() {
array = [...Array.from({ length: 5 }).keys()].map((i) => ({
id: i,
item: Promise.resolve({
id: i + 1,
name: `item_${i + 1}`,
render: 0,
}),
}));
}
init();
return () => console.log("destroyed");
});
</script>
{#if array && array.length}
{#each array as { id, item: promise } (id)}
{#await promise}
<p>...rendering</p>
{:then item}
<Item {item} on:click={() => render(item.id)} />
{/await}
{/each}
{/if}
Original answer
You haven't really made it clear what isn't working. You can run async calls in onMount
and await the promises in an {#await}
block.
Here's a contrived REPL that builds an array of promises, uses Promise.all
to resolve them and assigns the the resulting promise to variable to be rendered.
<script>
import { onMount } from "svelte";
let promise;
async function render(i) {
return {
id: i + 1,
name: `item_${i + 1}`,
};
}
onMount(() => {
const array = [...Array.from({length: 5}).keys()];
promise = Promise.all(array.map((i) => render(i)));
return () => console.log("destroyed");
});
</script>
{#await promise}
<p>...rendering</p>
{:then array}
{#if array && array.length}
{#each array as item (item.id)}
<p>
{item.name}
</p>
{/each}
{/if}
{:catch error}
<p>oh dear.</p>
{/await}