skip to Main Content

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 !

2

Answers


  1. Try to implement a simple locking mechanism.

    With that you can be sure if there’s an ongoing fetch operation for a specific URL, so the calls for the same URL will wait until the first fetch is completed.

    Here the example

    var arrContent = []; // Array of objects: {"url": "http://...", "content": "<div>..."}
    var content = null;
    var fetchLocks = {}; // Object to keep track of ongoing fetch operations
    
    this.loadFile = async function(url) {
        // Check if the content was already loaded
        let i = findContentIndex(url);
        if (i !== null) { // If the template already exists, load 
            console.log("Cached!");
            content = arrContent[i].content;
        } else {
            // If fetch is already in progress for this URL, wait 
            while (fetchLocks[url]) {
                await new Promise(resolve => setTimeout(resolve, 10));
            }
    
            // Recheck if the content was loaded while waiting
            i = findContentIndex(url);
            if (i !== null) {
                console.log("Cached!");
                content = arrContent[i].content;
                return;
            }
    
            // Lock the fetch operation for this URL
            fetchLocks[url] = true;
            console.log("Fetching...");
    
            try {
                const response = await fetch(url);
                const responseText = await response.text();
                content = responseText;
                arrContent.push({ "url": url, "content": content });
            } catch (error) {
                console.log(error);
            } finally {
                // Unlock the fetch operation for this URL
                delete fetchLocks[url];
            }
    
            console.log("Content");
        }
    };
    
    function findContentIndex(url) {
        for (let i = 0; i < arrContent.length; i++) {
            if (arrContent[i].url === url) {
                return i;
            }
        }
        return null;
    }
    

    The loop uses await new Promise(resolve => setTimeout(resolve, 10)) to periodically check the status….

    Login or Signup to reply.
  2. 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. 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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search