DEV Community

Jaime López
Jaime López

Posted on

⏱️ Caching for the win! How we solved a tough caching issue 🤓

Speed is everything in today's world, especially when it comes to web applications. Optimizing the application's performance is essential to provide a seamless user experience. One of the best ways to achieve this is by caching data that doesn't change frequently.

However, implementing caching can be a bit tricky, and it may cause issues when retrieving outdated data. In this article, we'll share how we tackled one of these problems by implementing a JavaScript cache in the browser. This approach not only enhanced the performance but also ensured that users have access to the latest data at all times. So, let's get into it!

Context

We faced an issue related to caching, which affected the performance of our web application. We had implemented caching to improve the application's speed, but we noticed that the application was retrieving outdated data from the cache. Even when new data was available on the server-side, the application would still retrieve the old data from the cache. This problem made our application look outdated and affected the user experience.

To solve this problem, we needed a solution that would allow us to update the cached data regularly and prevent the application from retrieving outdated data. We realized that the issue was related to the cache expiration period, which we had not set correctly. So, we decided to update our caching implementation to include a way to automatically refresh the cached data after a certain period. This way, we could ensure that the application always retrieved the most up-to-date data and provided a better user experience.

Solution

When we encountered the issue with our caching mechanism, we realized that we had overlooked a crucial factor: the time of the cache. Our application was never updating the cached data because it was always retrieving the same data as it did on the first load. We needed to find a way to make sure that the application always retrieved the latest data from the cache, and if the data had changed, it would be updated automatically.

To address this issue, we added a new variable to our caching mechanism that would store the time of the cache. Whenever the application retrieved data from the cache, it would check the cache time variable to determine if the cached data was still valid. If the cache time had expired, the application would reload the data from the source and update the cache with the new data. This ensured that the application always retrieved the latest data from the cache, which greatly improved the performance of our web application.

In addition to the cache time variable, we added a check to ensure that if the cache time variable did not exist, the application would assume that there was no cached data available and would load the data from the source. This was done to solve compatibility issues with previous versions of the code. Overall, these changes to our caching mechanism provided an efficient solution to our issue, which significantly improved the performance of our web application and ensured that users always had access to the latest data.

The Code

The provided code exports a JavaScript module that includes a function called CachedService. This function accepts two parameters, namely key and service, and returns an object that contains a method called get. It is important to note that the get method is an essential requirement for all services used in the application.

By using the CachedService function in this way, we can encapsulate existing services without losing compatibility. We will discuss this in more detail later in the article.

The code is self-explanatory and easy to understand. However, it's important to note that the CACHE_KEY constant value is intended to be the cache key and should be provided by the original service. Similarly, the CACHE_DATE_PROPERTY_NAME constant value is used as the key for the cache time, while the CACHE_ENTITY_PROPERTY_NAME constant value is used as the key of the entity to be stored in the cache.

Once you understand the purpose of these constants, the rest of the code is intuitive to follow. The get method first checks if the cached data is still valid by comparing the cache time with the current time. If the cached data is still valid, it returns the cached value. Otherwise, it retrieves new data from the source, updates the cache with the new data, and returns the new data.

// CachedService.js

import LoggerService from "./LoggerService";

export default function CachedService({ key, service }) {

    const logger = new LoggerService('CachedService');

    const CACHE_KEY = key;
    const CACHE_DATE_PROPERTY_NAME = 'cacheDateTime';
    const CACHE_ENTITY_PROPERTY_NAME = `cache${key}`;

    const get = async () => {
        // Obtaining the value from session storage
        const cacheValue = sessionStorage.getItem(CACHE_KEY);

        // Parsing the value retrieved
        const cacheData = cacheValue !== null && cacheValue !== undefined
            ? JSON.parse(cacheValue)
            : null;

        // Retrieving the date value from cache
        const cacheDateTime = cacheData !== null && Object.keys(cacheData).some(k => k === CACHE_DATE_PROPERTY_NAME)
            ? cacheData[CACHE_DATE_PROPERTY_NAME]
            : null;

        // Setting if cache expired
        const hasCacheExpired = cacheDateTime === null || new Date(cacheDateTime).getTime() <= Date.now();

        // Return the cache value if cache didn't expire
        if(!hasCacheExpired) {
            logger.debug(`Returning cache data for ${key}...`);
            return cacheData[CACHE_ENTITY_PROPERTY_NAME];
        }

        logger.debug(`Refreshing cache data for ${key}...`);

        // Get tools from API and then cache values
        const newTools = await service.get();

        // Setting cache time
        const newCacheDateTime = new Date(Date.now()).setMinutes(new Date(Date.now()).getMinutes() + 5);

        // Building cache entity
        const cacheToolsEntity = {};
        cacheToolsEntity[CACHE_DATE_PROPERTY_NAME] = newCacheDateTime;
        cacheToolsEntity[CACHE_ENTITY_PROPERTY_NAME] = newTools;

        // Saving cache entity in cache
        const newCacheValue = JSON.stringify(cacheToolsEntity);        
        sessionStorage.setItem(CACHE_KEY, newCacheValue);
        return newTools;
    };

    return {
        get
    };
}
Enter fullscreen mode Exit fullscreen mode

Let me explain how you can use the CachedService function in your service function. Imagine that you have an existing service function called RandomService that returns useful data. Now, you want to add caching functionality to this function without modifying any of its code. Here are the steps you need to follow:

  1. Rename the existing RandomService function to Service.
  2. Create a new function called RandomService that returns the CachedService function.
  3. When you create an instance of the CachedService function, you need to provide a unique key value for the cache, which cannot be the same as the one used in another service. You also need to provide a new instance of the Service function.
  4. Export the new RandomService function.

By following these steps, you can add caching functionality to your existing service function without making any changes to its code. The rest of the application will work as expected, and you will be able to improve its performance by using the cache.

// RandomService.js

import LoggerService from '../services/LoggerService';
import CachedService from '../services/CachedService';

function Service() {
    const logger = new LoggerService('RandomService');

    const get = async () => {
        // Retrieving data from an API service...
        var data = await fetch('/api/v1/#service name#').then(data => data.json());
        return data;
    };

    return {
        get
    };
}

export default function RandomService(params) {
    return new CachedService({
        key: 'random',
        service: new Service(params)
    });
};
Enter fullscreen mode Exit fullscreen mode

Conclusion

I hope you've found the CachedService function useful in your web application development. As you may know, this function helps improve the performance of your web application by caching data and avoiding unnecessary network requests.

However, as with any code, there's always room for improvement. That's why I'd like to encourage you to review the CachedService function and see if there are any areas where it could be optimized or enhanced.

Additionally, you may want to look into ways to make the CachedService function more customizable and flexible, so that it can be adapted to different use cases and scenarios. This could include options to customize the cache key format, the cache storage location, or the caching behavior.

So, I invite you to take a closer look at the CachedService function and think about how it could be improved. Whether you're an experienced developer or just starting out, your feedback and suggestions are always welcome and appreciated.

Thank you for your attention, and happy coding!


Don't forget to share the article with your friends and colleagues if you find it interesting, click on the heart if you like it, or click on the comments to share what you think of the article, if you would add more, or if you want to clarify any of them.


Top comments (2)

Collapse
 
karthick_rk profile image
Karthick • Edited

Can you please light it up, why cache control: max age is not used? (I assume it is a client side caching)

Collapse
 
jaloplo profile image
Jaime López

You are totally right. The use of the header would be easier than coding all this stuff. I've also checked Can I use page and it's supported in the majority of the browsers.

Time to code in the right way. Let's consider this code as an exercise instead of a final implementation.