DEV Community

loading...

Caching API requests in JavaScript

adancarrasco profile image Adán Carrasco Updated on ・3 min read

TLDR;

Consuming APIs is everyday's meal in JavaScript, this also has its limitations, amount of requests per second is the most common, in this case we are going to implement an algorithm to cache data based on time. Let's say we are sure that the data we are going to fetch is not going to change in a certain period of time then we can cache the data for that span. Some data can be cached by seconds, minutes, or even days.

For this example we are going to consume data from Open Weather API, this is an API to fetch the weather in different cities, for the weather we can say that it won't change every 10 minutes, so we can cache the data for this period of time.

Let's prepare our simple template to input the city and show the data on blur.

<!DOCTYPE html>
<html>
  <head>
    <title>Cache</title>
    <meta charset="UTF-8" />
  </head>

  <body>
    <div id="app">
      <h1>Hello Cache!</h1>
      <div>
        <input type="text" id="inputWeather" placeholder="Fetch weather for" />
      </div>
      <div id="container"></div>
    </div>

    <script src="src/index.js"></script>
  </body>
</html>

Our cache structure should store the metadata that we want to cache + the timestamp until the data will be cached, to make it easier to get/set the structure will be a HashMap. The key can be the city in lower case (if your keys can be duplicated you can use a more complex key).

const cache = {
    london: {
        ...metadata,
        cacheTimer: 1234567890
    },
    ...
}

Our regular function to fetch data would be something like:

async function fetchWeatherInfo(cityName) {
  let weatherInfo = await fetch(
    `https://api.openweathermap.org/data/2.5/weather?q=${cityName}&APPID=${api}`
  )
    .then(data => data.json())
    .then(myJson => myJson)
  return weatherInfo
}

Where we receive the city name and we do the fetch, returning the metadata. This function will be wrapped by the cache function, in the cache function we also receive the cityName + the time until the cache will be valid, if the hash key doesn't exist or if the time is lower than now then we are going to fetch the new data and cache it, the function will be like:

const cache = {}
let cacheTimer = 0

async function fetchWithCache(cityName, time) {
  const now = new Date().getTime()
  if (!cache[cityName] || cache[cityName].cacheTimer < now) {
    cache[cityName] = await fetchWeatherInfo(cityName)
    cache[cityName].cacheTimer = getCacheTimer(time)
  }
  return cache[cityName]
}

Inside the fetchWithCache function we are getting the cache timer, the cache timer is now's date + the time we want our data to be cached. Let's create the function to get the cache timer:

function getCacheTimer(time) {
  const now = new Date().getTime()
  if (cacheTimer < now + time) {
    cacheTimer = now + time
  }
  return cacheTimer
}

At this point we have the function to normally fetch our data, our function to set the timer we want our data to be cached and our function to cache the data. Let's create the function that will display the weather data in the HTML. To display data in the HTML we need to get the input element and set an event listener for the on change. The cacheTime is the value that we want our data to be persisted for, in this case is 100,000 milliseconds. In the event listener we are calling the displayWeatherData function, this function will call our cache function and get the data either from the cache or from the API request.

const input = document.getElementById("inputWeather")
const weatherContainer = document.getElementById("container")
const cacheTime = 100000

function init() {
  input.addEventListener("change", updateValue)
  function updateValue(e) {
    displayWeatherData(e.target.value)
  }
}

async function displayWeatherData(cityName) {
  const weatherInfo = await fetchWithCache(cityName.toLowerCase(), cacheTime)
  if (!weatherInfo || !weatherInfo.weather) {
    weatherContainer.innerHTML = `There's an error with request.`
    return
  }
  weatherContainer.innerHTML = `<p>${weatherInfo.name}</p><p>${weatherInfo.weather[0].main}<p><p>${weatherInfo.main.temp}</p><p>--------------</p>`
  console.log(cache)
}

init()

For debugging purposes I left the console.log statement you can check the Network tab in your browser DevTools and confirm that the request is only performed the first time, then it's cached for 10 seconds.

You can see it in action here: https://codesandbox.io/s/kind-dew-5bbrn

Thanks for reading!

Discussion

pic
Editor guide
Collapse
leoawesome profile image
Leo

Bro.
Is await with then 2 times the best practices??

Collapse
Sloan, the sloth mascot
Comment deleted
Collapse
luigicampbell profile image
luigicampbell

I think Leo means you can await the json call within the async function.

I think something like:

const res = await fetch(url);
const weatherInfo = await res.json();

return weatherInfo;
Thread Thread
adancarrasco profile image
Adán Carrasco Author

Thanks for the example luigicampbell!

Collapse
adancarrasco profile image
Adán Carrasco Author

Thanks for the feedback Leo! To be honest I haven't check the best practices for fetch. Do you have a link where I can take a look? :)

Collapse
fallenstedt profile image
Alex Fallenstedt

The cache with a timer is a great idea. I will consider this approach with NgRx for our teams Angular application. Thanks for the inspiration!

Collapse
vinchauhan profile image
vinchauhan

I think you can do the same with sharedReplay rxjs operator.

Collapse
adancarrasco profile image
Adán Carrasco Author

I'm glad it helps Alex! Would be good if you can share your implementation. :D

Collapse
marcelobarrera profile image
Marcelo

Gracias bro, good info

Collapse
ecodeslab profile image
Ecodes-lab

Hello I so much love the implementation, it's working on my pc..
But please how can make it work on mobile devices because i tried viewing from my Android device but it doesn't work, please help