Creating mobile Web applications with HTML 5, Part 4: Using Web Workers to speed up your mobile Web applications

Add multi-threaded JavaScript to HTML 5 for the win!

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 article, 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.

Michael Galpin, Software architect, eBay

Michael Galpin's photoMichael Galpin is an architect at eBay. He is a frequent contributor to developerWorks. He has spoken at various technical conferences, including JavaOne, EclipseCon, and AjaxWorld. To get a preview of what he is working on next, follow @michaelg on Twitter.



29 June 2010 (First published 08 June 2010)

Also available in Chinese Russian Japanese Vietnamese

29 Jun 2010: Added links to Part 5 of this series in About this series, Summary, and Resources sections.

About this series

HTML 5 is a very hyped technology, but with good reason. It promises to be a technological tipping point to bring desktop application capabilities to the browser. As promising as it is for traditional browsers, it has even more potential for mobile browsers. Even better, the most popular mobile browsers have already adopted and implemented many significant parts of the HTML 5 specification. In this five-part series, you will take a closer look at several of those new technologies that are part of HTML 5 and can have a huge impact on mobile Web application development. In each part, you will develop a working mobile Web application showcasing an HTML 5 feature that you can use on modern mobile Web browsers like the ones found on the iPhone and Android-based devices.


Getting started

In this article, you will develop Web applications using the latest Web technologies. Most of the code here is just HTML, JavaScript, and CSS—the core technologies of any Web developer. The most important thing you will need are browsers to test things on. Most of the code in this article will run on the latest desktop browsers with some noted exceptions. Of course, you must test on mobile browsers too, and you will want the latest iPhone and Android SDKs for those. In this article, iPhone SDK 3.1.3 and Android SDK 2.1 were used. This article's sample also uses a proxy server to access remote services from the browser. The proxy server is a simple Java™ servlet, but could be easily replaced by a proxy in PHP, Ruby, or others. See Resources for links.


Multi-threaded JavaScript on mobile devices

Multi-threaded or concurrent programming is nothing new to most developers. It is supported, in one way or another, in most modern programming languages. However, JavaScript is not one of the languages that supports concurrent programming. Its creator thought that it was too problematic and unnecessary for a language like JavaScript that was designed for performing simple tasks on Web pages. However, as Web pages have evolved into Web applications, the complexity level of tasks done with JavaScript has grown to put JavaScript on par with any other language. At the same time, developers working in other languages that support concurrent programming have often suffered and struggled with the extraordinarily high complexity that comes along with concurrent primitives like threads and mutexes. In fact, recently, a number of new languages like Scala, Clojure, and F# have evolved, all promising to simplify concurrency.

Frequently used acronyms

  • Ajax: Asynchronous JavaScript + XML
  • API: Application Programming Interface
  • CSS: Cascading stylesheet
  • DOM: Document Object Model
  • HTML: Hypertext Markup Language
  • REST: Representational State Transfer
  • SDK: Software Developer Kit
  • UI: User Interface
  • URL: Uniform Resource Locator
  • W3C: World Wide Web Consortium
  • XML: Extensible Markup Language

The Web Worker specification is not just about adding concurrency to JavaScript and Web browsers; it is about doing this in a smart way that will empower developers without giving them a tool that causes problems. For example, desktop application developers have used multi-threading for years to let their applications access I/O resources without freezing the UI while they wait for these resources. However, such applications often suffer when these multiple threads change shared resources (including the UI) which leads to applications that freeze up or crash. With Web Workers, this cannot happen. The spawned thread does not have access to the same resources as the main UI thread. In fact, the code in the spawned thread cannot even be in the same file as the code that is executed by the main UI thread.

You even have to supply that external file as part of the constructor, as in Listing 1.

This process uses three things:

  1. The JavaScript for the Web page (I will call it the page script) executed on the main thread.
  2. The Worker object which is the JavaScript object that you use to perform the Web Worker function.
  3. The script that will be executed on the newly-spawned thread. I will call it the Worker script.

Let's start by looking at the page script in Listing 1.

Listing 1. Using a Web Worker in the page script
var worker = new Worker("worker.js");
worker.onmessage = function(message){
    // do stuff
};
worker.postMessage(someDataToDoStuffWith);

In Listing 1, you can see the three basic steps in using Web Workers. First, you create a Worker object and pass it the URL of the script that will be executed in the new thread. All of the code that the Worker will execute must be contained in the Worker script whose URL is passed in to the constructor of the Worker. The URL of this Worker script is limited by the browser's same origin policy—it must come from the same domain that loaded the page that loaded the page script that is creating the Web Worker.

The next thing you do is to specify a callback handler function using the onmessage function. This callback function will be invoked after the Worker script executes. message is data that is returned from the Worker script and you can do whatever you want with that message. The callback function is executed on the main thread, so it has access to the DOM. The Worker script runs in a different thread and does not have access to the DOM, so you need to send data from the Worker script back to the main thread where you can safely modify the DOM to update the UI of your application. This is the key feature of the nothing-shared design of Web Workers.

The last line of Listing 1 shows how the Worker is initiated—by calling its postMessage function. Here you pass a message (again, this is just data) to the Worker. Of course, postMessage is an asynchronous function; you call it and it immediately returns.

Now, examine the Worker script. The code in Listing 2 is the contents of the worker.js file from Listing 1.

Listing 2. A Worker script
importScripts("utils.js");
var workerState = {};
onmessage = function(message){
     workerState = message.data;
      // do stuff with the message
    postMessage({responseXml: this.responseText});
}

You can see that the Worker script has its own onmessage function. This is invoked when you call postMessage from the main thread. The data you passed from the page script is passed to the postMessage function in the message object. You access the data by retrieving the data property of the message object. When you finish processing the data in the Worker script, you invoke the postMessage function to send data back to the main thread. That data is also available to the main thread by accessing the data property of the message it receives.

So far you have seen the simple, but powerful semantics of Web Workers. Next, you will see how to apply this to speed up mobile Web applications. Before getting into that, it's necessary to talk about device support. After all, these are mobile Web applications and dealing with the differences in capabilities between browsers is essential to mobile Web application development.

Device support

Starting with Android 2.0, the Android browser has had full support for the HTML 5 Web Worker specification. At the time of writing this article, most new Android devices, including the very popular Motorola Droid, are shipping with Android 2.1. In addition, this feature is fully supported in the Mozilla Fennec browser available on Nokia devices running its Maemo operating system, and on Windows Mobile devices. The notable omission here is the iPhone. iPhone OS versions 3.1.3 and 3.2 (the versions of the OS that run on the iPad) do not support Web Workers yet. However, this feature is already supported on Safari, so it should just be a matter of time before it shows up on the Mobile Safari browser running on the iPhone. Given the dominance of the iPhone (especially in the US) it is best to not rely on Web Workers being present and only use them to enhance your mobile Web applications when you detect that they are present. With this in mind, look at how you can use Web Workers to make mobile Web applications faster.


Improving performance with Workers

Web Worker support on smart phone browsers is good and improving. This begs the question: When do you want to use Workers in mobile Web applications? The answer is simple: whenever you need to do something that takes a long time. Some Worker examples show Workers being used to perform intense mathematical computations, like calculating ten-thousand digits of pi. It's fairly unlikely that you will ever need to perform such a computation on any Web application, much less a mobile Web application. However, retrieving data from remote resources is fairly common, and that is the focus of the example in this article.

In this example, you will retrieve a list of Daily Deals (deals that change each day)from eBay. The deals list contains brief information about each deal. More detailed information can be obtained by using eBay's Shopping API. You will use Web Workers to pre-fetch this additional information while the user browses the list of deals to pick the one of interest. To access all of this eBay data from your Web application, you will need to deal with the browser's same origin policy by using a generic proxy. A simple Java servlet was used for this proxy. It is included with the code for this article, but it is not shown here. Instead, let's focus on the code that works with the Web Workers. Listing 3 shows the basic HTML page for the deals application.

Listing 3. Deals application HTML
<!DOCTYPE HTML>
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <meta name = "viewport" content = "width = device-width">
    <title>Worker Deals</title>
    <script type="text/javascript" src="common.js"></script>
  </head>
  <body onload="loadDeals()">
    <h1>Deals</h1>
    <ol id="deals">
    </ol>
    <h2>More Deals</h2>
    <ul id="moreDeals">
    </ul>
  </body>
</html>

As you can see, this is a very simple piece of HTML; it is just a shell. You retrieve the data and generate the UI using JavaScript. This is the optimal design for mobile Web applications, since it allows for all of the code and static markup to be cached on the device, and the user only waits for data from the server. Notice that, in Listing 3, once the body has loaded, you call the loadDeals function where you load the initial data for the application in Listing 4.

Listing 4. The loadDeals function
var deals = [];
var sections = [];
var dealDetails = {};
var dealsUrl = "http://deals.ebay.com/feeds/xml";
function loadDeals(){
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function(){
        if (this.readyState == 4 && this.status == 200){
               var i = 0;
               var j = 0;
               var dealsXml = this.responseXML.firstChild;
               var childNode = {};
               for (i=0; i< dealsXml.childNodes.length;i++){
                   childNode = dealsXml.childNodes.item(i);
                   switch(childNode.localName){
                   case 'Item': 
                       deals.push(parseDeal(childNode));
                       break;
                   case "MoreDeals":
                       for (j=0;j<childNode.childNodes.length;j++){
                           var sectionXml= childNode.childNodes.item(j);
                           if (sectionXml && sectionXml.hasChildNodes()){
                               sections.push(parseSection(sectionXml));
                           }
                       }
                       break;    
                   default:
                       break;
                   }
               }
               deals.forEach(function(deal){
                   var entry = createDealUi(deal);
                   $("deals").appendChild(entry);
               });
               loadDetails(deals);
               sections.forEach(function(section){
                   var ui = createSectionUi(section);
                   $("moreDeals").appendChild(ui);
                   loadDetails(section.deals);
               });
        }
    };
    xhr.open("GET", "proxy?url=" + escape(dealsUrl));
    xhr.send(null);
}

Listing 4 shows the loadDeals function, and the global variables used in the application. You use an array of deals plus an array of sections. These are additional groups of related deals (for example, Deals under $10). There is also a map called dealDetails whose keys will be Item IDs (which you will get from the deals data), and whose values are the more detailed information obtained from the eBay Shopping API.

The first thing you do is make an Ajax call to a proxy, which in turn calls the eBay Daily Deals REST API. This gives you the list of deals as an XML document. You parse the document in the onreadystatechange function of the XMLHttpRequest object used to make the Ajax call. You use two other functions, parseDeal and parseSection, to parse the XML nodes into easier-to-use JavaScript objects. You can find these functions in the downloadable code sample (see Downloads), but they are just tedious XML parsing functions so I didn't include them here. Finally, after parsing the XML, you use two more functions, createDealUi and createSectionUi, to modify the DOM. When you are done, the UI looks like Figure 1.

Figure 1. Mobile Deals UI
Screen capture of Mobile Deals UI with sample deals, including a Show Details button for each deal

If you go back to Listing 4, notice that after the primary deals are loaded, you call the loadDetails function for each of the sections of deals. In this function, you load the additional details for each deal by using the eBay Shopping API—but only if the browser supports Web Workers. Listing 5 shows the loadDetails function.

Listing 5. Pre-fetching deals details
function loadDetails(items){
    if (!!window.Worker){
        items.forEach(function(item){
            var xmlStr = null;
            if (window.localStorage){
                xmlStr = localStorage.getItem(item.itemId);
            }
            if (xmlStr){
                var itemDetails = parseFromXml(xmlStr);
                dealDetails[itemDetails.id] = itemDetails;
            } else {
                var worker = new Worker("details.js");
                worker.onmessage = function(message){
                    var responseXmlStr =message.data.responseXml;
                    var itemDetails=parseFromXml(responseXmlStr);
                    if (window.localStorage){
                        localStorage.setItem(
                                        itemDetails.id, responseXmlStr);
                    }
                    dealDetails[itemDetails.id] = itemDetails;
                };
                    worker.postMessage(item.itemId);
            }
        });
    }
}

In loadDetails, you first check for the Worker function in the global scope (the window object.) If it is not there, then you simply do nothing. If it is there, then you first check the localStorage for the XML for this deal's details. This is a local caching strategy common to mobile Web applications, and is described in detail in Part 2 of this series (see Resources for a link).

If the XML is found locally, then you parse it in the parseFromXml function and add the details to the dealDetails object. If it is not found locally, then spawn a Web Worker and send it the Item ID of the deal using postMessage. Once that Worker retrieves the data and posts back to the main thread, you parse the XML, add the result to dealDetails, and store the XML in the localStorage. Listing 6 shows the Worker script, details.js.

Listing 6. Deal details Worker script
importScripts("common.js");
onmessage = function(message){
    var itemId = message.data;
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function(){
        if (this.readyState == 4 && this.status == 200){
            postMessage({responseXml: this.responseText});
        }
    };
    var urlStr = generateUrl(itemId);
    xhr.open("GET", "proxy?url=" + escape(urlStr));
    xhr.send(null);
}

The Worker script is pretty simple. You use Ajax to call the proxy, which in turn calls the eBay Shopping API. Once you receive the XML from the proxy, you send it back to the main thread using a JavaScript object literal. Note that, even though you can use XMLHttpRequest from a Worker, everything will come back on its responseText property, never its responseXml property. That is because there is no JavaScript DOM parser in scope within the Worker script. Note that the generateUrl function comes from the common.js file (in Listing 7). You imported common.js using the importScripts function.

Listing 7. Script imported by the Worker
function generateUrl(itemId){
    var appId = "YOUR APP ID GOES HERE";
    return "http://open.api.ebay.com/shopping?callname=GetSingleItem&"+
        "responseencoding=XML&appid=" + appId + "&siteid=0&version=665"
            +"&ItemID=" + itemId;
}

Now that you have seen how to populate the deal details (for browsers that support Web Workers), go back to Figure 1 to figure out how this is used in the application. Notice that each deal has a Show Details button next to it. Click it to modify the UI as in Figure 2.

Figure 2. Deal details displayed
Screen capture of deal details with item description, pictures, and price of two Golla (MP3, cell, and camera) pouches

This UI is displayed when you invoke the showDetails function. Listing 8 shows this function.

Listing 8. The showDetails function
function showDetails(id){
    var el = $(id);
    if (el.style.display == "block"){
        el.style.display = "none";
    } else {
        el.style.display = "block";
        if (!el.innerHTML){
            var details = dealDetails[id];
            if (details){
                var ui = createDetailUi(details);
                el.appendChild(ui);
            } else {
                var itemId = id;
                var xhr = new XMLHttpRequest();
                xhr.onreadystatechange = function(){
                    if (this.readyState == 4 && 
                                      this.status == 200){
                        var itemDetails = 
                                        parseFromXml(this.responseText);
                        if (window.localStorage){
                            localStorage.setItem(
                                              itemDetails.id, 
                                              this.responseText);
                        }
                        dealDetails[id] = itemDetails;
                        var ui = createDetailUi(itemDetails);
                        el.appendChild(ui);
                    }
                };
                var urlStr = generateUrl(id);
                xhr.open("GET", "proxy?url=" + escape(urlStr));
                xhr.send(null);                        
            }
        }
    }
}

You are passed the ID of the deal that is going to be shown and toggle whether it is displayed or not. When it is first called, it checks to see if the details have already been stored in the dealDetails map. If the browser supports Web Workers, then these details are already stored there and the UI for it is created and added to the DOM. If the details are not loaded already, or if the browser does not support Workers, then you make an Ajax call to load this data. This is how the application can work equally well whether Workers are present or not. This means that, if Workers are supported, then the data is already loaded and the UI will respond instantly. If there are no Workers, the UI still loads, but it takes a few seconds.


Summary

Web Workers sound like an exotic new technology for Web developers. But, as you have seen in this article, they have very practical applications. This is especially true for mobile Web applications. The Workers can be used to prefetch data or perform other ahead-of-time operations to provide a much more lively UI. This can be especially true on mobile Web applications that need to load data over a potentially slow network. Combine this with caching strategies, and your users will be amazed by the snappiness of your application.


Download

DescriptionNameSize
Article source codeWorkers.zip8KB

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into XML on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=XML, Open source, Web development, Mobile development
ArticleID=494479
ArticleTitle=Creating mobile Web applications with HTML 5, Part 4: Using Web Workers to speed up your mobile Web applications
publish-date=06292010