DEV Community

Cover image for Cache API in JavaScript - with just 20 lines of code.
Rajesh Royal
Rajesh Royal

Posted on • Updated on

Cache API in JavaScript - with just 20 lines of code.

let cache = {};
async function getData(url){
    let result = "";
    if(cache[url] !== undefined) return cache[url].value;

    await fetch(url)
    .then(response => response.json())
    .then(json => cache[url] = {time: new Date(), value: json});

    return cache[url].value;
}

// Interval to clear cache;
setInterval(function (){
    if(Object.keys(cache).length > 0){
        let currentTime = new Date();
        Object.keys(cache).forEach(key => {
            let seconds = currentTime - cache[key].time;

            if(seconds > 10000){
                delete cache[key];
                console.log(`${key}'s cache deleted`)
            }
        })
    }
}, 3000);
Enter fullscreen mode Exit fullscreen mode

Now you can call your API's like this.

getData("https://jsonplaceholder.typicode.com/todos/1")
.then(data => console.log(data));
Enter fullscreen mode Exit fullscreen mode

If there is a cache value of the current api call then it will return values from cache otherwise call the api and return data while adding the response to cache.

Preview

Image Cache API in JavaScript - with just 20 lines of code

I'm assuming this is much better than RTK Query and React Query 😅.

Discussion (24)

Collapse
rouilj profile image
John P. Rouillard

Just out of curiosity why are you calling:

let currentTime = new Date();
Enter fullscreen mode Exit fullscreen mode

inside of .forEach? Why not assign it before the call:

 let currentTime = new Date();
 Object.keys(cache).forEach(key => {
            let seconds = currentTime - cache[key].time;
            ...
Enter fullscreen mode Exit fullscreen mode

this way you only pay for the overhead of calling Date once.

I expect new Date() to be practically a constant (maybe 100ms difference) over the time spent clearing the cache. Since every three seconds you are clearing anything in the cache older than 10 seconds.

Calling new Date() IIUC is expensive since it has to reach down to the operating system to get the date value.

True some item might linger in the cache for 100 ms longer but....

Collapse
rajeshroyal profile image
Rajesh Royal Author

Good correction, I will update the code.

Also I didn't write it to be optimised. I was just experimenting how caching works simply and pasted same code here 😀.

Collapse
milanwake profile image
Moon Presence

It's good that you shared interesting material, but remember that not all developers know, are thinking and/or working on optimizing their scripts. There are beginners, and perhaps not only beginners, who, seeing interesting code at first glance, copy it into their applications and use it without checking or optimizing, and then other people may suffer from sudden errors that can come out absolutely by accident.

It is necessary to approach operations related to cycles carefully and try to think through the work of the function in advance, since even any small error can entail a bunch of others that can cause unnecessary memory costs or something else, perhaps. Lol.

I wish you good luck.

Thread Thread
rajeshroyal profile image
Rajesh Royal Author

Sure brother.

Collapse
lezzowski profile image
Omid

Can I ask you why are you doing this:

await fetch(url)
    .then(response => response.json())
    .then(json => cache[url] = {time: new Date(), value: json})
Enter fullscreen mode Exit fullscreen mode

Instead of this:

const response = await fetch(url)
cache[url] = { time: new Date(), value: response.json()
Enter fullscreen mode Exit fullscreen mode

If you use await you can replace those .then and I think this is much more cleaner :D

Collapse
rajeshroyal profile image
Rajesh Royal Author

Good Improvement. I will update the code.

Collapse
lezzowski profile image
Omid • Edited on

I think you can append .json() to this:

const response = await fetch(url).json()
Enter fullscreen mode Exit fullscreen mode

(I didn't test it but I think it should work)
Happy to help you! :D

Thread Thread
rajeshroyal profile image
Rajesh Royal Author

No it cannot be done.

Thread Thread
gohomewho profile image
Gohomewho

Actually that doesn't work and you'll get an error.
Uncaught TypeError: fetch(...).json is not a function
Because fetch() return a promise, and it doesn't have a json method on it.

It should be like this.

const url = "https://jsonplaceholder.typicode.com/todos/1"
const data = await fetch(url).then((response)=>response.json())
Enter fullscreen mode Exit fullscreen mode

or this

const response = await fetch(url)
const data = await response.json()
Enter fullscreen mode Exit fullscreen mode

Note that calling response.json() also return a promise so you need to await it to get the result.

Collapse
eyalshalev profile image
Eyal Shalev • Edited on

Note: if you need caching for production projects, I highly recommend searching for existing implementations (or at least existing algorithms).

Otherwise I have some notes:

  1. The current implementation only accepts the URL. But usually the same URL is used with different parameters, http methods or headers. Because not all of the possible keys can be mapped, I recommend accepting a key, and a function (to be used if the key is missing).
  2. Some data will need to be cached for different amount of time, so accepting a parameter for it is a good idea. That would mean using a setInterval for each key, or replacing the cache[key].time with a validUntil time object.
  3. Because you are deleting the cache after a fixed point of time, there is no need to keep the cache centralized, and it will be easier to just implement a memoize function that deleted the cache after a certain amount of time.
function memoize(fn, delAfter = 1000000) {
  const missing = Symbol("missing");
  let cache = missing;
  return async () => {
    if (cache === missing) {
       cache = Promise.resolve(fn());
       setTimeout(() => cache = missing, delAfter);
    }
    return await cache;
  }
}
const foo = memoize(() => fetch("https://foo/bar", { method: " POST", body: JSON.stringify({bar:1}) }))

// Will only do a single fetch.
console.log(await foo(), await foo())

Enter fullscreen mode Exit fullscreen mode
Collapse
rajeshroyal profile image
Rajesh Royal Author

restep++

Collapse
piixiiees profile image
Jose Gascon • Edited on

Nice post, thanks for sharing.
What do you think about integrating the clearing of the cache in the getData function?
Performance wise it should be very similar and the result is even more compact (just 10 lines 😄)

const cache = {};

async function getData(url){
  const currentTime = new Date();
  if(cache[url] !== undefined) {
    if((currentTime - cache[url].time) < 10000) return cache[url].value;
    delete cache[url];
  }
  await fetch(url)
    .then(response => response.json())
    .then(json => cache[url] = {time: currentTime, value: json});
  return cache[url].value;
}
Enter fullscreen mode Exit fullscreen mode
Collapse
rajeshroyal profile image
Rajesh Royal Author

➕➕

Collapse
kleky profile image
Ian Klek • Edited on

First rule of software development: see if it's been done before - don't reinvent the wheel. Check out the Cache interface, which would provide you with caching across browser sessions: developer.mozilla.org/en-US/docs/W...

Collapse
rajeshroyal profile image
Rajesh Royal Author

Chill out man, Its just an experiment of curiosity which I felt good to share.

Collapse
shikhar13012001 profile image
Shikhar

Nice idea to use Memoized results.Thanks!!

Collapse
timhuang profile image
Timothy Huang

Great job. Thanks for sharing.

Collapse
rajeshroyal profile image
Rajesh Royal Author

👍👍

Collapse
snelson1 profile image
Sophia Nelson

Thanks, great read.

Collapse
aaravrrrrrr profile image
Aarav Reddy

Nice post

Collapse
rajeshroyal profile image
Rajesh Royal Author

Thanks @aaravrrrrrr

Collapse
dinerdas profile image
Diner Das

Cool!

Collapse
ggaulard profile image
Guillaume Gaulard

What happen if you get the same data at the same url very closely, the first call didn't finished so you make two calls instead of one

Collapse
rajeshroyal profile image
Rajesh Royal Author

Didn't understand much, can you please explain a bit more.