It appears that the fetch operation does not wait as expected

Having some trouble with a caching system in my code where I'm trying to load the same template quickly. Even though I've tried implementing a basic caching mechanism to only fetch the template if it hasn't been loaded before, I'm struggling with getting the await to actually wait until the function is done.

This is the section of code responsible for loading the template:

    var arrContent = []; //array of objects: {"url":"http://...", "content": "<div>..."}
    var content = null;
    this.loadFile = async function(url){
        
        //Checking if the content has already been loaded
        let i = findContentIndex(url);
        if(i != null){ // The template already exists, so we just load it from memory
            console.log("Cached!");
            content = arrContent[i].content;
        }else{
            //If the content is not in cache, we fetch 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(){
    //...
}

After running the code multiple times within milliseconds of each other, I end up with an array containing duplicates of the same template URL, even though the code should be preventing duplicates.

Here's how the calls are being made for context:

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

The output ends up looking like this:

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

Instead of:

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

If the entire LoadFile function could execute just once at a time, it would solve the issue I'm facing.

Thank you!

Answer №1

The issue arises from the fact that your caching system only becomes effective after the result has been fully received, rather than during the loading process. The solution is to store the promise itself in the cache and do so immediately upon initiating the request.

/** Array of objects: {"url":"http://...", "promise": Promise<"<div>...">} */
const cache  = [];
this.loadFile = function(url){
    // Check if the content is already being loaded
    let entry = cache.find(e => e.url === url);
    if (entry != null) {
        // The template is already loading, simply return the existing promise again
        console.log("Cached!");
    } else {
        // Content is not yet in the cache, fetch it from the specified 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;
}

I have also modified the function to return the (promise for the) content instead of storing it in a shared variable (which can lead to issues when loadFile and render are invoked multiple times within async code). To utilize the function, follow these steps:

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

Additionally, I would suggest utilizing a Map for the cache instead of an array of objects.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Selecting a full table row activates a top-up popup

I have successfully implemented a script in Javascript to enable full table row selection as shown below. <script type="text/javascript"> $(function() { $('#link-table td:first-child').hide(); $('#link-table tr').hover(func ...

Is there a way to dynamically register an external component in Vue3 without altering the main project?

In my current project, known as "ProjectMain," I am also working on another project that will act as an extension to ProjectMain - let's call it "MyComponent." My intention is to create MyComponent as a standalone library. My main question is: can I ...

Transforming javascript's array.map into php's array_map function

The following code snippet is written in JavaScript and consists of a hashmap array with keys and values. I have created a function using map that returns the values of the entered keys. var rule = { "c": "d", "a": "o", "t": "g", "h": "a", "e": "n", "n": ...

MongooseError: Attempting to execute a query that has already been completed: user.findOneAndUpdate(`

I've encountered an issue while following an online tutorial. The error I'm facing with the "PATCH" function (followUser) persists even after copying the instructor's code. The strange part is that it successfully updates in mongoDB despite ...

Fade in and out MaterialUI text using useEffect in combination with setInterval

I have implemented a text carousel using MaterialUI's Fade component. The carousel displays text from an array provided in a prop called dataArray. To achieve the carousel effect, I am toggling the boolean value of the Fade component and updating the ...

Is there a way to trigger a confirmation function for form submission exclusively when clicking one specific submit button, and not the other?

Here is the layout of my form: <form action="newsletter.php" name="newsletter" id="newsletter" method="post"> <input type="submit" value="Submit" class="c-btn" id="submit_value" name="submit_value"> <input type="submit" value="Send" cla ...

What is the best way to retrieve the initial element from a map containing certain data?

I am attempting to retrieve the first image path directory from an API that contains an Image so I can assign the value to the Image source and display the initial image. However, when using fl[0], all values are returned. Below is the code snippet: {useL ...

Adding clickable text to dynamically generated tables using JavaScript

I've created a dynamic table populated with data from a JSON response. This table consists of 3 columns - one for Serial Number, another for Name, and the third column is meant to have two clickable text links labeled as Edit and Delete. When clickin ...

Exploring the dynamic world through HTML5 canvas and animated objects

Today I am exploring HTML 5 canvas and experimenting with moving 3 circles on the canvas. Based on my research, it looks like I need to continuously redraw the circles (perhaps every 60 milliseconds) and clear out the old circle before rendering the new on ...

What is the best way to add randomness to the background colors of mapped elements?

I am looking for a way to randomly change the background color of each element However, when I try to implement it in the code below, the background color ends up being transparent: { modules.map((module, index) => ( <div className='carou ...

What is the best way to trigger useEffect when the state being used within the effect is expected to change during the course of the effect's

Exploring the following code snippet: const [list, setList] = useState([]); const [curPage, setCurPage] = useState(0); const fetchItem = useCallback(async ()=>{ const data = await callAPI(); // data is an object setList(prev => [...prev, data]) ...

I am sending JSON as form data using JavaScript and then accessing it in PHP. During this process, the quotation marks are being replaced with their HTML entity equivalent

After converting an array into JSON, I send it as a value of a text box using the post method. In a PHP file, when trying to print it out, it displays some encoding issues. var json_arr = JSON.stringify(info); The generated JSON looks like this: {"1":"1 ...

Using a function as a prop in Vue js to retrieve data from an API

I am facing an issue with a component that I want to decouple from the data fetching implementation. My goal is to be able to pass a data fetching callback as a prop. The reason for this is so that I can easily mock the data fetching process in storybook. ...

Jquery Position behaving unexpectedly during initial invocation

My goal is to have the autocomplete menu open above the input box if there is not enough space below it. The code functions properly, except for the initial render. It consistently displays at the bottom in the following scenarios: 1. When starting a searc ...

Issue in CakePHP after modifying the directory of Index

I'm currently working on a project using CakePHP and have come across an issue. Our team developed an Ajax function that sends data to a PHP function responsible for adding a folder (known as "ordner" in German) to the database. Initially, everything ...

Loop through associative array in PHP using JQuery

I have a PHP associative array and I am using JQuery AJAX to retrieve the result array. My issue arises when passing the result to jQuery and attempting to loop through and extract each Sequence, Percent, and Date. I need to store this extracted data in a ...

Finding a nested div within another div using text that is not tagged with XPath

I need help creating an XPath to select a div with the class "membername" using the parameter "Laura". <div class="label"> <div class="membername"></div> David </div> <div class="label"> < ...

Access file using operating system's pre-installed application

How can I open a file using the default application for that file type on different operating systems? For example, when opening an image.png on Mac, it should open with Preview, and on Windows with Windows Photo Viewer. I know you can use open image.png ...

Invoke cloud functions independently of waiting for a response

Attempting a clever workaround with cloud functions, but struggling to pinpoint the problem. Currently utilizing now.sh for hosting serverless functions and aiming to invoke one function from another. Let's assume there are two functions defined, fet ...

When using CasperJS to capture multiple screenshots, the most recent screenshot will replace all previous ones

Exploring CasperJS has been a great experience for me. Despite my enjoyment, I've encountered an issue with casper.capture() that has me stumped. I've set it up to capture screenshots whenever a test fails and placed it in a separate setup module ...