Saturday, October 5, 2024

Cache in JavaScript



Caching is a technique used to store data in a temporary storage area called a cache. Caching aims to retrieve data quickly and avoid costly processes such as recalculating or searching it in the database, especially for frequently asked resources. Thus, caching helps improve the performance of multiple applications, including web applications. Caching helps decrease the time required to access data. JavaScript offers various possibilities for performing data caching or resources for a short or long time. In this article, I want to present how you can cache your data or resources in JavaScript. I explain how to choose the appropriate caching possibility according to your needs.


1. Common Caching Techniques in JavaScript

JavaScript offers a variety of caching techniques, each suited to specific situations. This may depend on the volume of data you want to cache, the nature of the data, and how long you need to persist your data. Here are the most common techniques to cache data in JavaScript.


- Web Storage API (localStorage and sessionStorage): This technique is the best fit to cache small pieces of data like default or user preferences values for search inputs or selected items for drop-down lists. Web Storage API is a technique to store data using the concept of key-value pair. It includes two types of storage,  localStorage and sessionStorage. LocalStorage is used to cache data permanently. However, sessionStorage allows data to be stored only for a session until the browser is closed.


- In-memory JavaScript Objects: It is used to persist data for a short time, like the result of API calls in a single session. The data will be deleted from the cache after the page refreshes. For example, if you have a dropdown list and every time you select an item (employee), you make an Ajax call to retrieve their data (birth date, email, etc.) from the backend. It will be better to cache this data. Every time you come back in the same single session to select an employee you have already selected, your code will fetch the data from the cache. It is more efficient, faster, and without loading the server.


- Service Workers: This technique is used especially to cache static resources (HTML, CSS, etc.) or API responses. It helps enhance the responsiveness and performance of the web application.


- IndexedDB: It is a low-level API to store large amounts of data, which is good for complex applications that need to cache large JSON responses or images. Thus, it allows working offline.


In this article, we through in detail the first technique and leave the others for other articles.


2. Comparison between Caching Methods

Here is a comparison table between the four above techniques. We consider in our comparison the main criteria such as data persistence time, storage capacity, use cases, the type of data that can be cached, and performance.

Technique

Web Storage API (localStorage/sessionStorag)

In-memory JavaScript Objects

Service Workers Cache API

IndexedDB

Data Persistence

- Persistent for localStorage

- Temporary for sessionStorage (only for a session)

Temporary (only when page is active)

Persistent (even after closing the page)

Persistent (even after closing the page)

Storage Capacity




- Limited and depends on the browser

- Usually 5-10 MB

Limited to available memory

- Depends on the browser

- Commonly larger than Web Storage

- Virtually unlimited

- Limits set by the browser

Data Structure Support


- String only

- Objects need manual serialization to be cached

Any data structure

Requests and responses (for assets like HTML, CSS, JS, etc.)

Complex data structures

Use Case

Storing small amounts of data like user preferences or settings

Caching data like the result of API calls for fast access in a single page load

Caching static elements like HTML, CSS, JS for offline use or to speed up loading

Storing large datasets like a client-side database,

large JSON responses, or images

Performance

Fast read/write for small data

Fastest data reading/writing technique because data is stored in memory

Slower than in-memory

Slower than in-memory or localStorage because it caches a large amount of data


3. Web Storage API (LocalStorage and SessionStorage)

LocalStorage and sessionStorage are used to cache a small amount of data. They store data in the form of key-value pairs. SessionStorage keeps data only for the current session. In other words, it saves data until you close the browser or tab. However, localStorage keeps data for a long time, even if you close the browser.


3.1. Main Properties and Methods

In the table below you will find the principal localStorage properties and methods you need to know to cache data. Note that sessionStorage has the same properties and methods.


LocalStorage Properties

Property

Definition

Syntax

length

Returns the count of the key-value pairs in the localStorage.

localStorage.length

LocalStorage Methods

Method

Definition

Syntax

clear

Removes all key-value pairs in storage.

localStorage.clear()

getItem

- Retrieve the value corresponding to the key.

- If the key does not exist, it returns null.

- If the cached value is a serialized object, you can convert it from a string to a JavaScript object using JSON.parse.

localStorage.getItem(‘key’)

setItem

- Stores a key-value pair.

- key and value are stored as strings.

- to store an object, you should convert it to a string using JSON.stringify.

localStorage.setItem(‘key’, value)

removeItem

Removes the key-value pair from the storage.

localStorage.removeItem(‘key’)

key

- Returns the name of the key at a specific index in the storage.

- localStorage saves key-value pairs in the order they were inserted.

localStorage.key(keyindex)



3.2. Example of Caching Data Using LocalStorage

Let’s take an example to show how to use localStorage to cache data. We are developing a small web application with Python's Flask framework for the backend and HTML, CSS, and JavaScript for the frontend.


Note. We only write the relevant part here to explain how to use the Web Storage API to cache data. If you want the full example, you can find it on GitHub here:

https://github.com/noura-github/webstorage-app-blog


Python Code

In the backend code, we first define ‘countries’ as a list containing a collection of dictionaries, where each dictionary represents a country and includes details such as its ID, name, capital, population, area, and dialing code.


countries = [

    {'Id': 10, 'Name': 'Austria', 'Capital': 'Vienna', 'Population': '9M', 'Area': '83,879 km2', 'Dialing_code': '+43'},

    {'Id': 20, 'Name': 'Germany', 'Capital': 'Berlin', 'Population': '84M', 'Area': '357,592 km2',

     'Dialing_code': '+49'},

    {'Id': 30, 'Name': 'France', 'Capital': 'Paris', 'Population': '67M', 'Area': '551,695 km2', 'Dialing_code': '+33'},

    {'Id': 40, 'Name': 'Italy', 'Capital': 'Rome', 'Population': '58M', 'Area': '301,230 km2', 'Dialing_code': '+39'},

    {'Id': 50, 'Name': 'Spain', 'Capital': 'Madrid', 'Population': '47M', 'Area': '505,990 km2', 'Dialing_code': '+34'},

    {'Id': 60, 'Name': 'England', 'Capital': 'London', 'Population': '56M', 'Area': '130,279 km2',

     'Dialing_code': '+44'},

]


In this Python code, we define two methods that we explain as follows:


Method ‘load_country_detail’: To return these details in response to an AJAX POST request, we define a Flask route in /load_country_detail. It fetches the JSON data from the request, extracts the country ID, and searches for the corresponding country details in the predefined list of countries using the ‘search’ method. Finally, it returns the country details as a JSON response.


Method ‘search’: The search method takes two parameters, ‘ct_id’ and ‘ls’. It searches for a country in the ‘ls’ list whose ‘id’ matches the provided ‘ct_id’. If a match is found, it returns the details of that country as a dictionary. If no match is found, it returns None.


@app.route('/load_country_detail', methods=['POST'])

def load_country_detail():

    # Get JSON data from request

    data = request.get_json()


    country_id = data.get('countryId')

    country_detail = search(int(country_id), countries)

    print("Found country detail: ", country_detail)


    # Send a JSON response back to the client

    return jsonify({

        'status': 'success',

        'country_detail': country_detail

    })


def search(ct_id, ls):

    result = [element for element in ls if element['Id'] == ct_id]

    if len(result) > 0:

        return result[0]

    return None


HTML Code

In the frontend code, we define an HTML file with some elements like a dropdown list containing the list of some European countries and a table to display the details of each country when the user selects it.


<body>

    <div class="container">

        <h1>List of European Countries</h1>

        <select id="selectCountry" class="select-country" onchange="onChangeCountry()" size="1">

            <option value="10">Austria</option>

            <option value="20">Germany</option>

            <option value="30">France</option>

            <option value="40">Italy</option>

            <option value="50">Spain</option>

            <option value="60">England</option>

        </select>


        <br><br><br><br>

        <div id="country_detail">

            <table id="country_table">

                <thead>

                    <tr>

                        <th>Country Name</th>

                        <th>Capital</th>

                        <th>Population</th>

                        <th>Area</th>

                        <th>Dialing Code</th>

                    </tr>

                </thead>

                <tbody>

                </tbody>

            </table>

        </div>

    </div>

</body>


JavaScript Code

In the JavaScript code, we implement the code of the ‘onChangeCountry()’ and other behaviors to demonstrate the usage of localStorage as a data caching mechanism. In summary, the code handles the loading, storing, and displaying country details. Here's a breakdown of the key functions.


We add a handler to the ‘DOMContentLoaded’ event to ensure that the JavaScript program starts once the HTML page is completely loaded and the elements are created. Within this handler, we call the ‘loadCountry()’ function.


document.addEventListener('DOMContentLoaded', function() {

   loadCountry();

}, false);


Function ‘loadCountry()’: This function retrieves the country name value from the browser’s localStorage, where the data can be stored persistently even after a page reload. If the element does not exist, the ‘storedCountry’ will be null.

If ‘storedCountry’ is not null, the function sets the drop-down value to the stored value. This ensures that when the page reloads, the drop-down will display the previously selected country.


function loadCountry() {

  let storedCountry = localStorage.getItem("country");

  console.log("Value of country in localStorage is:", storedCountry)

  console.log(document.getElementById("selectCountry").value)

  if (storedCountry){

     document.getElementById("selectCountry").value = storedCountry;

  } else {

     localStorage.setItem("country", document.getElementById("selectCountry").value);

  }

  onChangeCountry();

}


Function ‘onChangeCountry()’: This function is called when the selected country is changed. It updates the localStorage with the value of the selected country. Here we use the string ‘country’ as a key to save the country ID in the localStorage.

Then the function fetches the data for the selected country from localStorage using the country ID. If the data is found, we need to convert it to an object using the built-in JavaScript function ‘JSON.parse()’. This function takes a JSON string and converts it into a JavaScript object. If the country data is not found, the ‘getCountryDetail()’ function will be called.


function onChangeCountry(){

    let selValue = document.getElementById("selectCountry").value;

    console.log("Store the value:", selValue, " in localStorage")

    localStorage.setItem("country", document.getElementById("selectCountry").value);


    let id = document.getElementById("selectCountry").value;

    let country_detail = localStorage.getItem(id);

    console.log("country_detail:", country_detail);

    if (country_detail){

        showCountryDetail(JSON.parse(country_detail));

    } else {

        getCountryDetail(document.getElementById("selectCountry").value);

    }

}


Function ‘getCountryDetail(countryId)’: This function sends a POST request to '/load_country_detail' endpoint with the selected country ID, retrieves the country details, stores them in localStorage, and updates the country details table. To cache an object in localStorage, we use the country ID as a key, and then we need to serialize the object using the built-in function ‘JSON.stringify()’ which converts the object to a string.


function getCountryDetail(countryId) {

    const dataToSend = {

        countryId: countryId

    };


    // Sending a POST request using fetch

    fetch('/load_country_detail', {

        method: 'POST',

        headers: {

            'Content-Type': 'application/json'

        },

        body: JSON.stringify(dataToSend)

    })

    .then(response => response.json())

    .then(data => {

        console.log('Success:', data);

        localStorage.setItem(data.country_detail["Id"], JSON.stringify(data.country_detail));

        showCountryDetail(data.country_detail);

    })

    .catch((error) => {

        console.error('Error:', error);

    });

}


Function ‘showCountryDetail(country_detail)’: This function dynamically creates a row in the country details table with information such as country name, capital, population, area, and dialing code. Let’s note that we create only one row every time we insert country data. So, we clear the table’s body before adding the new row.


Function ‘findRow(tBody, country_name)’: This function checks if a row with the given country name already exists in the table to avoid duplicates.


Function ‘addCell(tr, item)’: This function adds a cell with a given content to a table row.


When you execute this code and you opened the browser, you find the following interface:




4. Web Storage API in Chrome DevTools

Open Chrome DevTools and go to the Application tab. Here you can manage localStorage and sessionStorage. You can clear them. You can also edit, delete any key-value pair, or look at what is inside. This is very useful for debugging your web application.








Blog Posts

Enhancing Performance of Java-Web Applications

Applications built with a Java back-end, a relational database (such as Oracle or MySQL), and a JavaScript-based front-end form a common and...