Accelerate the value of multicloud with collaborative DevSecOps Learn more

Speed up your mobile web applications with HTML5 and Web Workers

HTML5 is a popular technology since its early days and for a very good reason. It brought desktop application-like capabilities to the web browser — not just on the desktop but also on mobile devices.

In this five-part series, we will take a look at some of the popular features that are part of HTML5. In each part, we will also write some code to showcase these features and see how they work on both desktop and mobile browsers.

Web applications have traditionally been stuck in a single-threaded world. This really limited developers in what they could do in their code, since anything too complicated risks freezing up the UI of the application. Web Workers have changed all of that by bringing multi-threading to Web applications. This is particularly useful for mobile Web applications where most of the application logic is client-side. In this tutorial, you will learn how to work with Web Workers and discover which tasks are most appropriate for them. You will see how you can use with other HTML 5 technologies to increase the efficiency of using those technologies.

Getting started

In this tutorial, you will develop a web application to showcase the web workers. The code in this tutorial is using core web technologies like HTML, CSS (Cascading Style Sheets), and JavaScript. To test the application on desktop and mobile, we recommend using the latest web browser versions.

Multi-threaded JavaScript on mobile devices

With advancements in browser technology and various new features that are now a part of web standards, websites have evolved into web applications with functionality on par with native apps. These web apps do a lot of things: they render user interfaces, make API calls, process data, handle UI events, and manipulate the DOM. Many programming languages support multi-threading or concurrency in one way or another. JavaScript as a language is single threaded; in simple terms, it can only do one thing at a time and multiple scripts cannot run at the same time. So, if our web app is executing a statement or a block of statements, which takes a long time to complete, all subsequent statements will hang up until that statement(s) is processed.

One way developers have solved this problem is by using asynchronous functions, which are non-blocking, but not concurrent. Luckily, HTML5 has provided a better solution. The web worker specification provides a standard API for spawning new background threads and enables concurrency in web apps. Web workers execute scripts in isolated threads and do not share resources with the main UI thread. The script executed by the workers has to be in a separate file. This allows web workers to perform their tasks without interfering with a main thread and without interrupting the user interface. Let’s look at the code in Listing 1.

Listing 1. Using a web worker in the page script

const worker = new Worker("worker.js");
worker.onmessage = function(message){
    const data = message.data;
    // do stuff
};
worker.postMessage(someDataToDoStuffWith);

As you can see, the constructor of the worker takes the name of the script to execute as an argument and returns a Worker object, which represents the newly created worker.

It’s important to note that web workers have access to a subset of JavaScript features. Web workers do not have access to a parent object, window or document object, and cannot access the DOM. So with a limited feature set, how do you use them, and how can you send and receive data between the main thread and the worker? To understand this, look at the code below.

Listing 2. Sample web worker script worker.js

onmessage = function(message) {
 const data = message.data;
  // do some processing
 postMessage(result);
};

The communication between the worker and the main UI thread is done using an event model. To send a message or some data to the worker, the main thread calls worker.postMessage(), as shown in Listing 1. This will invoke the onmessage function inside the worker script, as shown in Listing 2. Workers can access the data by retrieving the data property of the message object.

Workers can use the data sent by the main thread, do some processing, and send the result back to the main thread using the postMessage function in the worker script. This will invoke the onmessage function in Listing 1 and the main thread will receive the message sent by the worker.

The web workers API is simple, but quite powerful. In the next section we will see how to use workers to build web apps with better performance.

Improving performance with workers

Web workers are a perfect candidate for anything that takes time to process and may not be required by the user straight away. Our web app can spawn a new thread, perform the task, and display the results to the user when it is available, without slowing down the app and affecting user experience.

Let’s imagine you are building a web portal where a user can access various information. One piece of the information you want to display is the current temperature based on a user’s location. While this information is useful, you do not want to keep the user waiting. You can use web worker to retrieve weather information in the background and display it to the user when it is available.

We discussed the geolocation API and how to get a user’s location in the first tutorial of this series. We will take the example we built and enhance it to use web workers.

Listing 3. Local weather information using geolocation API

<!DOCTYPE html>
<html>
  <head>
    <title>Local Weather</title>
    <script type="text/javascript">
      const initialize = () => {
        navigator.geolocation.getCurrentPosition(
          getWeatherInfoByLocation,
          handleError
        );
      };

      const handleError = error => {
        alert(`Unable to retrieve location: ${error.message}`);
      };

      const getWeatherInfoByLocation = position => {
        const xhr = new XMLHttpRequest();
        const lat = position.coords.latitude;
        const long = position.coords.longitude;
        const url = `https://api.openweathermap.org/data/2.5/weather?APPID=<APP_ID>⪫=${lat}&lon=${long}`;
        xhr.onreadystatechange = function() {
          if (xhr.readyState === 4) {
            showTemperature(JSON.parse(xhr.response));
          }
        };

        xhr.open("get", url, true);
        xhr.send();
      };

      const showTemperature = weatherInfo => {
        const location = weatherInfo.name;
        const temperature = Math.round(
          ((weatherInfo.main.temp - 273.15) * 9) / 5 + 32
        );
        document.getElementById(
          "weatherInfo"
        ).innerHTML = `Current temperature in ${location} is ${temperature}°Fahrenheit `;
      };
    </script>
  </head>
  <body onload="initialize()">
    <div>
      <h1 id="weatherInfo">Retriving local weather information...</h1>
    </div>
  </body>
</html>

In Listing 3, we have the code built in the first tutorial. On page load, we call the initialize function, which uses the geolocation API getCurrentPosition function to get the user’s location and then retrieve weather information using the user’s geolocation coordinates. We are using the Open Weather Map API for this example. Please note that you will need to create an account with Open Weather Map and generate an API key, which is required to call Open Weather Map APIs. The free account is limited, but should be enough for our simple application.

You will take the code in Listing 3 and enhance it to use web workers. You will implement the functionality in a way that if a browser does not support web workers, you can still handle it and provide a good experience to your users.

Before you start, it is important to note that worker threads have limited access to the geolocation navigator object, and you cannot use the getCurrentPosition function to retrieve user location within the worker thread. So we will use the parent thread to retrieve the user location and pass it to the worker thread for further processing.

Let’s start by extracting the code to retrieve weather information into a separate file util.js.

Listing 4. util.js

const getWeatherInfoByLocation = (location, callback) => {
  const xhr = new XMLHttpRequest();
  const url = `http://api.openweathermap.org/data/2.5/weather?APPID=<API_KEY>⪫=${location.latitude}&lon=${location.longitude}`;
  xhr.onreadystatechange = function() {
    if (xhr.readyState === 4) {
      callback(JSON.parse(xhr.response));
    }
  };

  xhr.open("get", url, true);
  xhr.send();
};

We have moved the getWeatherInfoByLocation function into the util file. The function now takes two parameters: the location object with latitude and longitude properties and a callback function that will be called once we receive the data from the API. Remember to replace API_KEY with your own Open Weather Map API key.

Next, we will update the web page to use util.js.

Listing 5. Updated local weather information page

<html>
  <head>
    <title>Local Weather</title>
    <script src="util.js"></script>

    <script type="text/javascript">
      const initialize = () => {
        navigator.geolocation.getCurrentPosition(handleSuccess, handleError);
      };

      const handleSuccess = (position) => {
        const location = position.coords;
        getWeatherInfoByLocation(location, showTemperature);
      }

      const handleError = error => {
        alert(`Unable to retrieve location: ${error.message}`);
      };

      const showTemperature = weatherInfo => {
        const location = weatherInfo.name;
        const temperature = ((weatherInfo.main.temp - 273.15) * 9) / 5 + 32;

        document.getElementById(
          "weatherInfo"
        ).innerHTML = `Current temperature in ${location} is ${temperature}°Fahrenheit `;
      };
    </script>
  </head>
  <body onload="initialize()">
    <div>
      <h1 id="weatherInfo">Retriving local weather information...</h1>
    </div>
  </body>
</html>

We have made a few changes in the code. First, we loaded the util.js file, which gives us the getWeatherInfoByLocation function. Then we updated our initialize function to call the handleSuccess function when the user location has successfully been retrieved. The handleSuccess function returns the call getWeatherInfoByLocation function, passing the geolocation coordinates and showTemperature function as the callback. When the weather information is retrieved by the util function, it will invoke the callback showTemperature function, which, as before, will write this information to the DOM.

Now let’s create our worker script.

Listing 6. Worker script

importScripts("util.js");

onmessage = (message) => {
  const location = message.data
  getWeatherInfoByLocation(location, postMessage);
};

The worker script is very simple: It has the onmessage function, which can receive a message from the main thread. In our case, the message will have the user location information. The worker script uses the same util script util.js as used by the main thread. On receiving the user location, it will call the getWeatherInfoByLocation function, passing the user location and worker’s postMessage function as the callback. This will ensure that once the weather information is received it will be passed to the parent thread.

Now it’s time to tie things together. Let’s update the handleSuccess function to use web workers when available.

Listing 7. Worker script

const handleSuccess = position => {
    const location = position.coords;
    if (window.Worker) {
      const myWorker = new Worker("worker.js");
      myWorker.postMessage(location);
      myWorker.onmessage = message => {
        showTemperature(message.data);
      };
    } else {
      getWeatherInfoByLocation(location, showTemperature);
    }
};

As shown in Listing 7, you first check if the browser supports web workers. If it does, create a new worker thread and pass the user’s location to the worker using the myWorker.postMessage function. This will invoke the onmessage inside the worker script. When the worker has finished processing, it will post its postMessage function, which will invoke the onmessage function in the main thread When the main thread receives the message from the worker, it will pass the weather information to the showTemperature function for DOM manipulation.

In our example, we used web workers to improve our web app’s performance by spawning new worker threads and performing some of the tasks outside the main thread. We also ensured that in case workers are not supported by any browser, we can still retrieve weather information and display it to our user. Although this is a simple example, you can use the same concept in complex scenarios.

Summary

Web workers may sound like a complex concept, but as you have seen in this tutorial, they are quite easy to use. Web workers can be used in pre-fetching data or performing tasks in background. Combine this with other HTML5 features, like local storage and offline support, and you can build super performant and progressive web applications.

Acknowledgements

This article was originally written by Michael Galpin and published on June 8, 2010.