I am building some functionality in Zendesk using mostly vanilla JS (jQuery is available too) and I have a button that needs to run an API call to get some data first before it can do its "real" job. But the async data is static - it will never change per user or session - so I was thinking to preload it as soon as the page loads, to save some time later. But the button should not assume the data has been loaded, even if it probably will be 90% of the time. I don't want to set up any annoying race conditions.
In the past, in similar scenarios, I have handled this by disabling the button by default and enabling it once the async data has loaded. But I was just wondering if there was a better pattern.
When you load data asynchronously with a modern API (like fetch
) then you will be using Promises to manage the asynchronicity.
Store the promise in your cache when you start to load it.
Then, when you want to use it (e.g. by clicking on the button) you can just await
the stored Promise. If the precaching has finished it will already be in a resolved state. If it is still pending then the operation will pause as normal until the data is available.
If you're using an older API (like XMLHttpRequest
) then you can wrap the operation in a Promise manually.
const fetchData = () => {
return new Promise((resolve) => {
console.log("Simulating a long running data fetching operation");
let percent_done = 0;
(function fetchChunk() {
percent_done += 10;
console.log(`Loaded ${percent_done}%`);
if (percent_done >= 100) resolve("Here is your data");
else setTimeout(fetchChunk, 1000);
})();
});
};
const cache = fetchData();
const logData = async() => {
const clickTime = new Date();
const data = await cache;
const now = new Date();
console.log(`Data was "${data}" from click at ${clickTime.toLocaleTimeString()} (time now is ${now.toLocaleTimeString()})`);
};
document.querySelector("button").addEventListener('click', logData);
<button>Click me</button>