I have noticed that the “hen and egg” problem will occur now and then using Javascript. If I load a stored menu by fetch, I often cannot use this menu until it is “finished”. And async will make it more unpredictable as the load time is different.
My intention is to fetch menus from database and then serve them dynamically from the menus in localStorage. But I must be sure that localStore is ready to use.
Is it possible to preload data ONCE and use this stored data in a predictable way?
async function get_menu() {
let url = "https://api3.go4webdev.org/main/std";
await fetch(url)
.then(response => response.json())
.then(data => localStorage.setItem('main', data))
}
get_menu()
alert(localStorage.main) <--- Not loaded first time in a new browser...
check to see if localStorage contains an item called main. If it does, call make_menu. If not, call get_menu, which should, as part of its then, call make_menu.
Notably, this isnt dynamic. It’s a single prefetch.
If your database connection is that slow that it’s noticable, you’ve got other issues.
If it’s not, load every time.
You could try a middle-ground approach (“timed cache”), wherein you store the menu for a certain TTL; if they need the menu again before TTL, serve from localstorage, else get new (and set new TTL).
Do not repeat yourself (DRY) if possible. Leave as small footprint as possible to the environment (less traffic, less CPU, less memory etc). Make it as fast as possible (memory is about the fastest). Less code used to fetch from localStorage than fetch from database. Etc…
Load the menu for each page x 1000 users may cause a lot of traffic, or?
Maybe I got it wrong, but this is my way of thinking.
Then parse the retrieved data, maybe something along the lines of
const menu = localStorage.getItem('main') // returns null if no 'main' in storage
return (menu !== null)
? JSON.parse(menu) // parse the JSON to JS objects
: fetchMainMenu() // fetch from the DB
Second, this
get_menu()
alert(localStorage.main)
get_menu is a non-blocking asynchronous function that returns a promise. What that means is the rest of the code e.g. alert(localStorage.main) will be executed before the returned promise is fullfilled.
By using a thenable, you can execute a callback once the promise is fullfilled — simple speak, once get_menu has finished then execute a callback.
So this instead
get_menu() // async functions return a promise
.then(() => {
const menu = localStorage.getItem('main')
// just for a quick test
if (menu !== null) {
console.log(JSON.parse(menu))
} else {
console.log('No menu found!')
}
})
// or if inside another function
const anotherFunction = async () => {
await get_menu()
const menu = localStorage.getItem('main')
...
}
edit: Just a small point
Use const instead of let
const response = await fetch("https://api3.go4webdev.org/main/std")
const data = await response.json()
let for values that are going to change e.g. let count = 0; count += 2
According to my tests, it works for the whole session until you close the window. But I will test further to confirm this. But switching tab is sort of “close” to me.
I think this work now. I have a sessionStore.clear() to ensure that it really works. Commenting this line reduce the flickering a lot (loading from sessionStore instead)
I was having a bit of a play with your jsfiddle @sibertius
Actually couldn’t figure out why sessionStorage wasn’t working until I spotted.
sessionStorage.clear() //to ensure that it work (remove)
I tested your code using vscode and liveserver, and ran into a problem. It seems to me checking for the length on sessionStorage could potentially be problematic. In the case of liveserver it adds its own entry to sessionStorage, so in my tests the check against length told me I had a stored menu, when I didn’t.
If you can guarantee no other scripts in your page are going to use sessionStorage, then fine. Otherwise it might be safer to actually check for the ‘main’ property instead.
Just for your consideration
const load_menu = async () => {
try {
const response = await fetch('https://api3.go4webdev.org/menu/std')
const menu = await response.json()
sessionStorage.setItem('main', JSON.stringify(menu))
return menu // Why not return the menu?
} catch (err) {
console.error(err)
}
}
const get_menu = async () => {
// start by trying to get the menu from storage
const storedMenu = sessionStorage.getItem('main')
// if storedMenu is null
// ? assign to menu the returned menu from load_menu()
// : otherwise assign the parsed storedMenu
const menu = (storedMenu === null)
? await load_menu()
: JSON.parse(storedMenu)
fillmenu(menu)
}