Search code examples
javascriptsveltesveltekitretry-logic

Sveltekit - loading spinner for multiple load-sources with auto-retry


I'm trying to make a spinner for sveltekit pages that is not too complex, but not sure what would be the simplest way to do it.

When page loads, it load maplibre map. It can fail during that process - so I need to show a spinner and if request fails - I retry the request after 5 seconds. After maplibre map has been loaded, I still do not hide the spinner since I need to load additional data with fetch. This time also I have to retry if request fails (and keep retrying until loaded). Only when everything is loaded I should hide the spinner.

This is the sample code:

onMount(() => {
    let map = new Map({}); //maplibre map
    map.on('load', function(){
        let data = fetch('url').catch((e)=>{
            //means additional load failed - retry in 5 sec.
        });
    });
    map.on('error', function(){
        //means map load failed - retry in 5 sec.
    });
});

I think that retrying logic makes everything much more complex, since I need to keep reference to each loading function and call it again. Not sure if there is already some best-practice solution for this use-case.


Solution

  • Not sure about the best practice, but this is what I would do:

    • Make sure each retryable thing is its own Promise
    • Use a resiliency library, like cockatiel
    • Inside onMount(), call each promise sequentially using the resiliency library

    In your case, it would look something like this:

    import { retry, handleAll, ConstantBackoff } from 'cockatiel';
    import { onDestroy, onMount } from 'svelte';
    
    const retryPolicy = retry(handleAll, { maxAttempts: 2, backoff: new ConstantBackoff(50) });
    
    let map;
    function loadMap() {
        map = new Map({});
        const promise = new Promise((resolve, reject) => {
            map.on('load', function () { resolve(); });
            map.on('error', function () { reject(); });
        });
        return promise;
    }
    
    const abortController = new AbortController();
    
    onMount(async () => {
        try {
            await retryPolicy.execute(() => loadMap(), abortController.signal);
            let data = await retryPolicy.execute(() => fetch('url'), abortController.signal);
        } catch {
            if (abortController.signal.aborted) return;
            // TODO: Handle case where all retries failed
        }
    });
    
    onDestroy(() => {
        abortController.abort(); // Will stop the retries
    });
    

    If you don't like async/await, you can rewrite with .then() and .catch().