Use Web Workers to improve the usability of your web applications

With Web Workers, a new JavaScript programming model, you can improve your web application's interactivity. You can run JavaScript in a multi-threaded way, and you can run scripts in the background independent of any user interface scripts. This article provides an introduction to Web Workers and walks you through a practical example that shows you how to adopt Web Workers into your own web applications.

Share:

Wei (Marshall) Shi, Software Engineer, IBM

Wei Shi photoWei Shi joined IBM in 2006 and currently works at the IBM China Development Lab. He works on IBM Lotus products design and development. Wei has in-depth experience on Web 2.0 and J2EE application development.



Bo Wang, Software Engineer, IBM

Photo of Bo WangBo Wang is a software engineer intern at the IBM China Development Lab. He works on IBM Lotus development. Bo is interested in Ajax and Web 2.0 application development.



09 November 2010

Also available in Chinese Japanese

Introduction

With the advent of Ajax and Web 2.0 applications, end users have been spoiled with the fast response of web applications. Before web applications can respond even faster, certain bottlenecks must be removed. Bottlenecks include the huge computation of JavaScript and background I/O, which need to be removed from the main UI rendering process. Enter Web Workers.

Develop skills on this topic

This content is part of a progressive knowledge path for advancing your skills. See HTML5 fundamentals

The Web Workers specification provides the capability to run scripts in the background independent of any user interface scripts. Long-running scripts won't be interrupted by scripts that respond to clicks or other user interactions. Web Workers allow long tasks to be executed, without yielding, while keeping the page responsive.

Before Web Workers, JavaScript was at the core of modern web applications. JavaScript and the DOM that it talks to are essentially single-threaded: only one JavaScript method can be executed at any given time. Even if your computer has four cores, it will keep only one of the cores busy when it is doing a long computation. For example, if you calculate the perfect trajectory to get to the moon, your browser won't be able to render an animation that shows the trajectory and—at the same time—react to user events (such as mouse clicks or keyboard typing).

Originally part of HTML 5, the Web Workers API was split into its own specification (see Resources). A few browsers, such as Firefox 3.5, Chrome 3, and Safari 4, implement most of the APIs of Web Workers. Click here to see how well your browser supports Web Workers.

Web Workers break the traditional JavaScript single-thread mode and introduce the multi-thread programming model. A worker is a stand-alone thread. Web applications that have many tasks to handle no longer need to process the tasks one by one. Instead, the application can assign the tasks to different workers.

In this article, learn about the Web Workers API. A practical example walks you through the steps for using Web Workers to quickly render a web page.

Download the source code for the example used in this article from the Download table below.


Basic concepts

The basic components of Web Workers are:

Worker
A new thread, running in the background, that will not block any main user interface scripts. Workers (as these background scripts are called) are relatively heavyweight and are not intended to be used in large numbers.

A worker can do quite a few jobs, including parallel computation, background I/O, and client-side database operation. The worker is not supposed to interrupt the main UI or manipulate DOM directly; it should return a message to the main thread and let the main thread update the main UI.

Subworker
A worker created within a worker. Subworkers must be hosted within the same origin as the parent page. The URIs for subworkers are resolved relative to the parent worker's location rather than that of the owning page.
Shared worker
A worker that can be used by multiple pages via multiple connections. The shared worker works slightly differently compared to a normal worker. Only a few browsers support this feature.

Web Workers API

This section introduces the basics of the Web Workers API.

Creating a worker

To create a new worker, you simply call the worker constructor with the worker script URI as the only parameter. Once the worker is created, a new thread (or possibly a new process depending on the implementation of your browser) is started at the same time.

When the worker finishes the work or encounters an error, you can get notifications from the worker with the onmessage and onerror properties of the work instance. Listing 1 shows a sample worker.

Listing 1. Sample worker myWorker.js
 // receive a message from the main JavaScript thread
onmessage = function(event) {
// do something in this worker
var info = event.data;
postMessage(info + “ from worker!”);
};

If you run the JavaScript code in Listing 2 below, you'll get the result "Hello World from worker."

Listing 2. Worker in main JavaScript thread
// create a new worker
var myWorker = new Worker("myWorker.js");
// send a message to start the worker
var info = “Hello World”;
myWorker.postMessage(info);
// receive a message from the worker
myWorker.onmessage = function (event) {
// do something when receiving a message from worker
alert(event.data);
};

Terminating a worker

A worker is a thread (or process, in essence) that is a high-resource-consumption OS-level object. When the task assigned to the worker is finished, or you just want to kill it, you need to call the worker's terminate method to terminate the running worker. The worker thread or process is killed immediately without an opportunity to complete its operations or clean itself up. Listing 3 shows an example.

Listing 3. Terminating myWorker
myWorker.terminate();

Handling errors

Similar to typical JavaScript code, a runtime error can occur in a running worker. To handle errors, you need to set the onerror handler for the worker, which will be invoked if any errors occur during the script running in a worker. The event doesn't bubble, and you can cancel it. To prevent the default action from taking place, the worker can call the error event's preventDefault() method.

Listing 4. Add error handler for myWorker
myWorker.onerror = function(event){
console.log(event.message);
console.log(event.filename);
console.log(event.lineno);
}

The error event has the following three fields that might be helpful for debugging purposes:

  • message: A human-readable error message
  • filename: The name of the script file in which the error occurred
  • lineno: The script file line number on which the error occurred

Importing scripts and libraries

Worker threads have access to a global function, importScripts(), which lets them import scripts or libraries into their scope. It accepts as parameters zero or more URIs of resources to import.

Listing 5. Importing scripts
//import nothing
importScripts();
//import just graph.js
importScripts('graph.js');
//import two scripts
importScripts('graph.js', 'controller.js');

Using Web Workers

This section walks you through a practical use case of Web Workers. The example involves rendering a page that contains several Dojo-based Website Displayer widgets. The widgets are used to display a website using iFrame. Without Web Workers, you would have to get the widget definitions by Ajax request and then render them in a single JavaScript thread. This would be very slow if the widget definition contained a huge amount of data.

The example creates some workers to fetch the widget definition. Each worker is assigned the task to fetch one widget definition and is responsible for telling the main UI JavaScript thread to render it. Because the workers can work in parallel, it is a much faster solution.

Dojo 1.4 is used for the example. If you want to run the example in your own browser, download the Dojo library (see Resources) and the source code (see Downloads) used in this article. Figure 1 shows the structure of the example application.

Figure 1. Web Workers application
Screen shot of the directory structure under the 'Web Workers' directory

In Figure 1:

  • lib is the dojo libraries.
  • /widgets/WebsiteDisplayer.js is a dojo-based Website Displayer widget implementation.
  • /loadwidget/widgets/widgetDefinition[0....3] are the definitions of each Website Displayer widget.
  • /loadwidget/Workers.js is the worker implementation.
  • /loadwidget/XMLHttpRequest.js is a js lib that contains a method to create XMLHttpRequst.
  • /loadwidget/LoadWidget.html is the main page of the demo with Web Workers enabled, which will be the main JavaScript thread.
  • /loadwidget/LoadWidget-none-web-workers.html is the main page that is implemented without Web Workers.

Creating a Website Displayer widget

The Website Displayer widget is a very simple Dojo-TitlePane-dijit-based widget. It will render the UI of a formalized title pane, as shown in Figure 2.

Figure 2. Website Displayer widget
Screen shot of the Website Displayer widget, with title 'Load Widget Use Web Workers'

Listing 6 shows the code for the WebsiteDisplayer.js.

Listing 6. Content of the WebsiteDisplayer.js
dojo.require("dijit._Widget");
dojo.require("dijit._Templated");
dojo.require("dijit.TitlePane");

dojo.declare("loadWidget.WebsiteDisplayer", [dijit.TitlePane], {
    title: "",
    url: "",
    postCreate: function() {
	var ifrm = dojo.create("iframe", {
           src: this.url,
	    style: "width:100%;height:20%;"
	});
	dojo.place(ifrm, this.domNode.children[1], "first");
	this.inherited(arguments);
	var contentFrame = this.domNode.children[1].children[0];
	if (contentFrame.attachEvent) {
	    contentFrame.attachEvent("onload",
		function() {
		    dojo.publish("frameEvent/loaded");
		}
	    );
	} else {
	    contentFrame.onload = function() {
		dojo.publish("frameEvent/loaded");
	    };
	}
    }
});

Creating a worker

To implement the worker.js, import a global JavaScript file called XMLHttpRequest.js, which contains the global method creatXMLHTTPRequest. This method will return an XMLHttpRequest object.

The worker will primarily send XMLHttpRequest to the server side and retrieve the widget definition back to the main JavaScript thread. Listing 7 and 8 show an example.

Listing 7. Content of Worker.js
importScripts("XMLHttpRequest.js");

onmessage = function(event) {
  var xhr = creatXMLHTTPRequest();
  xhr.open('GET', 'widgets/widgetDefinition' + event.data + '.xml', true);
  xhr.send(null);
  xhr.onreadystatechange = function() {
    if (xhr.readyState == 4) {
      if (xhr.status == 200 || xhr.status ==0) {
	 postMessage(xhr.responseText);
      } else {
	 throw xhr.status + xhr.responseText;
      }
    } 
  }
}
Listing 8. widgetDefinition0.xml
<div dojoType="loadWidget.WebsiteDisplayer" title="This is Test Widget 0"
   url="http://www.yahoo.com" ></div>

Creating the main web page

The main web page is where you: create several workers; send messages to workers and start the workers; receive messages from workers; and manipulate the main UI with the received messages.

Listing 9. Main web page
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>
            Load widgets with Web Workers
        </title>
        <style type="text/css">
            @import "../lib/dijit/themes/soria/soria.css";
	    @import "../lib/dojo/resources/dojo.css";
	    @import "../lib/dojox/layout/resources/GridContainer.css";
            @import "../lib/dojox/layout/resources/DndGridContainer.css"
        </style>
        <script type="text/javascript" src="../lib/dojo/dojo.js" 
           djConfig="parseOnLoad: true,isDebug:true">
        </script>
        <script>
            dojo.require("dojo.parser");
            dojo.require("dojo.io.script");
	     dojo.require("dojox.layout.GridContainer");
            dojo.require("dijit.layout.LayoutContainer");
            dojo.require("dijit.TitlePane");
            dojo.require("dojox.layout.DragPane");
            dojo.registerModulePath("loadWidget", "../../loadWidget");
            dojo.require("loadWidget.WebsiteDisplayer");
	</script>
	<script type="text/javascript" language="javascript">
            var workersCount = 4;
            var haveLoadedCount = 0;
            var widgetCount = 4;
            var startTime = new Date().getTime();
            var endTime = null;
            var executeTime = 0;
            try {
                for (var i = 0; i < workersCount; i++) {
                    var loadWorker = new Worker("Worker.js");
                    loadWorker.postMessage(i);
                    loadWorker.onmessage = processReturnWidgetDefinition;
					loadWorker.onerror = handleWorkerError;
                }
            } catch(ex) {
                console.log(ex);
            }
			
            function processReturnWidgetDefinition(event) {
                var txt = document.createElement("p");
                txt.innerHTML = event.data;
                var div = document.getElementById("loadingDiv");
                div.appendChild(txt);
                haveLoadedCount++;
                if (haveLoadedCount == widgetCount) {
                    dojo.parser.parse();
                }
            }
			
	     function handleWorkerError(event){
		  console.log(event.message);
	     }
			
            dojo.subscribe("frameEvent/loaded", dojo.hitch(null, handelFrameLoaded));

            function handelFrameLoaded() {
                if (haveLoadedCount == widgetCount) {
                    endTime = new Date().getTime();
                    executeTime = endTime - startTime;
                    dojo.byId("loading").innerHTML = "Loading cost time:" + executeTime;
                }
            }
        </script>
    </head>
	
    <body class="soria">
       <div dojoType="dijit.TitlePane" title="Load widgets with Web Workers" 
          style="border: 2px solid black; padding: 10px;"
        id="main">
            <div id="loadingDiv">
                <div id="loading">
                    Widgets are loading......
                </div>
            </div>
        </div>
    </body>
	
</html>

Embed this main page into a web application and run it. The results should look like Figure 3.

Figure 3. Load widgets with Web Workers
Screen shot of the three widgets, showing parts of three different websites

To see the difference between using Web Workers versus not using Web Workers, run LoadWidget.html and LoadWidget-none-web-workers.html separately and see the result. Note that the page running without Web Workers will finish faster than the one with Web Workers because the code sample handles so little data. The time saved is balanced against the cost of starting a worker.


Tips for using Web Workers

The previous example involves XMLHttpRequest and the computation; it is not very large or complex. When you give the worker more complicated tasks to do, such as handling large calculations, it becomes a very powerful feature. Before adopting this cool technology into your project, review the following tips.

Cannot access DOM in workers

For safety reasons, workers can't manipulate the HTML document directly. Multiple threads operating on the same DOM would create problems with thread security. An advantage is that you no longer have to worry about the multi-thread safety problems in worker implementation.

This situation has a few limitations when developing the worker. You cannot call alert() within the worker, which is a very popular way to debug JavaScript code. You also cannot call document.getElementById(), as it can only receive and return variables (which could be strings, arrays, JSON objects, and so on).

Objects available in workers

Though the worker can't access the window object, it can access the navigator directly. You can access the appName, appVersion, platform, and userAgent in the navigator object.

The location object can be accessed read-only. You can get the hostname and port inside of the location object.

XMLHttpRequest is also enabled in the worker, as shown in the example in this article. You can add a lot of interesting extensions into the worker because of this feature.

Also available are:

  • The importScripts() method (for accessing script files in the same domain)
  • JavaScript objects, such as Object, Array, Date, Math, and String
  • The setTimeout() and setInterval() methods

Data types carried in postMessage

postMessage is used quite heavily since it's the major method for the main JavaScript thread to interact with the workers. However, the data types that now could be carried in postMessage are limited to the native JavaScript types, such as Array, Date, Math, String, JSON, and so forth. The complicated customized JavaScript objects are not supported very well.


Download

DescriptionNameSize
Sample code for this articlesource.zip4KB

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 Web development on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Web development
ArticleID=569797
ArticleTitle=Use Web Workers to improve the usability of your web applications
publish-date=11092010