Search code examples
javascriptasync-awaitpromisefetch-api

await fetch does not seem to wait


I have some code calling to load the same template really fast. I've tried to implement a simple caching system to only fetch when the template wasn't previously loaded, but I can't seem to get the await to truly wait until the function is completed.

The code for the template loading system:

    var arrContent = []; //array of objects: {"url":"http://...", "content": "<div>..."}
    var content = null;
    this.loadFile = async function(url){
        
        //Check if the content was already loaded
        let i = findContentIndex(url);
        if(i != null){ // The template already exist, simply load it from memory
            console.log("Cached!");
            content = arrContent[i].content;
        }else{
            //Content is not already in cache, load it from the url
            console.log("Fetching...");
            await fetch(url)
                .then(function(response){
                    return response.text();
                })
                .then(function(response) {
                    content = response;
                    arrContent.push({"url": url, "content": content});
                })
                .catch( 
                    function() {
                        error =>  console.log(error);
                    }
                );
            console.log("content");
        }
}

function findContentIndex(url){
    for(let i=0; i<arrContent.length; i++)
        if(arrContent[i].url != undefined && arrContent[i].url == url)
            return i;
    return null;
}

this.render = function(){
    //...
}

What I end up is with an array containing many duplicate of the same template URL, where as the code should prevent duplicate from happening.

For context, the calls are made this way. Multiple calls within a few ms of each others:

await Tpl.loadFile(chrome.runtime.getURL("view/widget.html"));
let content = Tpl.render();

The output will look something like:

Fetching...
Fetching...
Fetching...
Fetching...
Fetching...
content
content
content
content
content

Instead of:

Fetching...
content
Cached!
Cached!
Cached!
Cached!

If I could have the entire LoadFile function executed just once at the time it would solve my problem.

Thank you !


Solution

  • The problem is that your caching system only works after the result has been received, but not while it still is loading. To fix this, store the promise itself in the cache, and store it immediately when starting the request.

    /** array of objects: {"url":"http://...", "promise": Promise<"<div>...">} */
    const cache  = [];
    this.loadFile = function(url){
        // Check if the content was already loading
        let entry = cache.find(e => e.url === url);
        if (entry != null) {
            // The template already started loading, simply return the same promise again
            console.log("Cached!");
        } else {
            // Content is not already in cache, load it from the url
            console.log("Fetching...");
            const promise = fetch(url)
                .then(response => {
                    if (!response.ok) throw new Error(response.statusText);
                    return response.text();
                });
            entry = { url, promise };
            cache.push(entry);
        }
        return entry.promise;
    }
    

    Notice I also changed the function to return the (promise for the) content, not store it in some shared variable (which is highly perilous when loadFile and render are called multiple times from async code). Then call the function as follows:

    try {
        const template = await Tpl.loadFile(chrome.runtime.getURL("view/widget.html"));
        const content = Tpl.render(template);
    } catch(error) {
        console.log(error);
    }
    

    Btw I'd also recommend to use a Map for the cache instead of an array of objects.