7 Things You Need To Know About Web Workers

Introduction

Web Workers allow you to run JavaScript code in the background without blocking the web page user interface. Web workers can improve the overall performance of a web page and also enhance the user experience. Web workers come in two flavors - dedicated web workers and shared web workers. This article discusses seven key aspects of web workers that you need to know if you decide to use them in your applications.

1. Web Workers Allow You to Run JavaScript Code in the Background

Typically the JavaScript code that you write in a web page executes in the same thread as the user interface. That's why when you click on a button that is supposed to trigger some lengthy processing, the web page user interface freezes. Unless the processing is complete you can't work with the user interface. Web workers allow you to execute JavaScript in the background so that the user interface remains responsive even if some script is being executed. The background thread in which this script is executed is often called a worker thread or worker. You can spawn as many workers as you wish. You can also pass data to the script being executed in the worker threads and also return value to the main thread upon completion. There are, however, some restrictions on the web workers as listed below:

Web workers can't access DOM elements from the web page.

Web workers can't access global variables and JavaScript functions from the web page.

Web workers can't call alert() or confirm() functions.

Objects such as window, document and parent can't be accessed inside the web worker.

You can, however, use functions such as setTimeout(), setInterval(). You can also use XMLHttpRequest object to make Ajax requests to the server.

2. Web Workers Come in Two Types

Web workers come in two types: dedicated web workers and shared web workers. Dedicated web workers come into existence and die along with a web page that created them. That means a dedicated web worker created in a web page can't be accessed by multiple web pages. On the other hand, shared web workers are shared across multiple web pages. The Worker class represents a dedicated web worker whereas the SharedWorker class represents a shared web worker.

In many cases, dedicated web workers fulfill your needs. That's because usually you need to execute a web page specific script in a worker thread. Sometimes, however, you many need to execute a script inside a worker thread that is common to more than one web page. In such cases instead of creating many dedicated web workers, one in each page, you may go for shared web workers. A shared web worker created by a web page remains available to other web pages. It gets destroyed only when all the connections to it are closed. Shared web workers are a bit more complex to code than dedicated web workers.

3. Worker Object Represents a Dedicated Web Worker

Now that you know basics of web workers, let's see how to use dedicated web workers. The example discussed below assumes that you have created a web application using your favorite development tool and have also added jQuery and Modernizr libraries into its Script folder. Add an HTML page to the web application and key-in the following code to it:

<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> <script src="scripts/modernizr.js"></script> <script src="scripts/jquery-2.0.0.js"></script> <script type="text/javascript"> $(document).ready(function () { if (!Modernizr.webworkers) { alert("This browser doesn't support Web Workers!"); return; } $("#btnStart").click(function () { var worker = new Worker("scripts/lengthytask.js"); worker.addEventListener("message", function (evt) { alert(evt.data); },false); worker.postMessage(10000); }); }); </script> </head> <body> <form> <input type="button" id="btnStart" value="Start Processing" /> </form> </body> </html>

The above HTML page consists of a button (btnStart) that triggers some JavaScript processing. Notice that the web page has references to the Modernizr and jQuery libraries. The <script> block consists of the ready() method handler that in turn handles the click event of btnStart. The ready() handler first checks whether the browser supports web workers or not. This is done using Modernizr's webworkers property. If the browser doesn't support web workers, an error message is displayed to the user.

The code then wires the click event handler of btnStart. The click event handler code is important because it uses a Worker object to run a script in the background. The click event handler creates a Worker object and stores it in a local variable - worker. The path of the JavaScript file that is to be executed in the background is passed in the constructor. You will create LengthyTask.js shortly. Then, the code adds an event handler for the message event of the Worker object. The message event is raised when the target script (LengthyTask.js in this case) sends some value back to the web page. The message event handler function can access this returned value using evt.data property. Finally, the postMessage() method is called on the Worker object to trigger the execution of LengthyTask.js. The postMessage() method also allows you to pass data to the target script. In this example a number (10000) is passed to postMessage() that indicates the number of milliseconds for which the processing should continue. You could have pass any other data in the postMessage() call such as a JavaScript object or a string.

The LengthyTask.js file contains the code that is to be executed in the background and is shown below:

addEventListener("message", function (evt) { var date = new Date(); var currentDate = null; do { currentDate = new Date(); } while (currentDate - date < evt.data); postMessage(currentDate); }, false);

The above code handles the message event of the worker thread. The message event is raised when the main page calls the postMessage() method on the Worker object. The message event handler mimics a lengthy processing by running a do-while loop for certain milliseconds. The number of milliseconds for which this loop runs is passed from the main page (recollect the postMessage() discussed earlier). Thus evt.data returns 10000 in this example. Once the lengthy operation is complete the code calls postMessage() to send the result of processing back to the main page. In this example, the value of currentDate is passed (currentDate is a Date object).

If you run the main web page and click on the Start Processing button, you will get an alert() after 10 seconds. In the mean time the user interface of the page is not blocked and you can perform operations such as scrolling, clicking etc. indicating that the code from LengthyTask.js is running in the background.

4. SharedWorker Object Represents a Shared Web Worker

The preceding example uses a dedicated web worker. Let's convert the same example to use a shared web worker. A shared web worker is represented by SharedWorker object. The following code shows the modified version of the code from the main page:

$(document).ready(function () { if (!Modernizr.webworkers) { alert("This browser doesn't support Web Workers!"); return; } $("#btnStart").click(function () { var worker = new SharedWorker("scripts/sharedlengthytask.js"); worker.port.addEventListener("message", function (evt) { alert(evt.data); }, false); worker.port.start(); worker.port.postMessage(10000); }); });

Notice the code marked in bold letters. It creates an instance of SharedWorker and passes SharedLengthyTask.js in the constructor. You will create this file shortly. The code then wires the message event handler to the port object of the SharedWorker object. The message handler function does the same job as in the preceding example. Then the code calls start() method on the port object. Finally, postMessage() method is called on the port object to send data (10000) to the shared worker thread.

The SharedLengthyTask.js file contains the following code:

var port; addEventListener("connect", function (evt) { port = evt.ports[0]; port.addEventListener("message", function (evt) { var date = new Date(); var currentDate = null; do { currentDate = new Date(); } while (currentDate - date < evt.data); port.postMessage(currentDate); }, false); port.start(); }, false);

The code begins by declaring a variable named port for storing a reference of a port object. This time two events are handled - connect and message. The connect event is raised when a connection is being made to the shared web worker. The connect event handler grabs evt.port[0] object and stores it in the port variable declared earlier. It then wires the message event handler on the port object. The start() method of the port object is called to start listening for a message at that port. The message event handler is almost identical to the one you wrote in the preceding example except that it is attached to the port object. Also, postMessage() is called on the port object to send the result of processing to the main page.

5. Web Workers can use XMLHttpRequest to Communicate with the Server

At times a web worker may need to talk with the web server. For example, you may need data residing in some RDBMS for your client side processing. To accomplish such tasks you can use XMLHttpRequest object to make requests to server side resources. The overall process of instantiating a Worker object and handling of the message event remains the same. However, you need to make a GET or POST request to a server side resource. Consider the following code that illustrates how this can be done:

addEventListener("message", function (evt) { var xhr = new XMLHttpRequest(); xhr.open("GET", "lengthytaskhandler.ashx"); xhr.onload = function () { postMessage(xhr.responseText); }; xhr.send(); }, false);

The code shown above creates an instance of XMLHttpRequest object. It then calls open() method and specifies that a GET request be made to a server side resource LengthyTaskHandler.ashx, an ASP.NET generic handler. (Although this example uses an ASP.NET generic handler, you can use any other server side resource.). It then handles the load event of XMLHttpRequest object and calls postMessage(). The xhr.responseText acts as the parameter for postMessage(). The xhr,responseText will be the value returned by the ASP.NET generic handler as the response. The load event is raised when the request is completed.

The LengthyTaskHandler.ashx contains the following code:

namespace WebWorkersDemo { public class LengthyTaskHandler : IHttpHandler { public void ProcessRequest(HttpContext context) { System.Threading.Thread.Sleep(10000); context.Response.ContentType = "text/plain"; context.Response.Write("Processing successful!"); } public bool IsReusable { get { return false; } } } }

As you can see the ProcessRequest() mimics some lengthy processing by calling Sleep() method on the Thread class and blocks the execution for 10 seconds. Then it returns a success message "Processing successful!" to the caller. If you run the main web page after making these changes you will find that after 10 seconds an alert dialog is displayed with this success message.

6. You can Trap Unhandled Errors Using Error Event

If your web worker is doing some complex operation you may want to add error handling to the main web page code so that in case of any unhandled errors inside the worker an appropriate action can be taken. This can be done by handling the error event of the Worker object. The error event is raised whenever there is any unhandled error in the worker thread. The following code shows how this can be done:

$("#btnStart").click(function () { var worker = new Worker("scripts/lengthytask.js"); worker.addEventListener("message", function (evt) { alert(evt.data); }, false); worker.addEventListener("error", function (evt) { alert("Line #" + evt.lineno + " - " + evt.message + " in " + evt.filename); }, false); worker.postMessage(10000); });

As you can see from the above code an error handler has been wired to the error event of the worker object. The error handler function receives an event object that provides error information such as line number at which the error occured (evt.lineno), error message (evt.message) and file in which the error occured (evt.filename).

7. You can Terminate a Worker Using Terminate() Method

At times you may want to cancel the task being executed in a worker. To do so you can destroy a Worker by calling its terminate() method. Once a Worker is terminated you can't reuse or restart it. Of course, you can always create another Worker instance and use it. Remember, however, that terminate() immediately kills the worker and you don't get any chance to perform cleanup operations.

Summary

Web workers allow you to execute a script in the background without freezing the web page user interface. They come in two types - dedicated web workers and shared web workers. A dedicated web worker is created per web page basis whereas a shared web worker is shared across multiple web pages. The Worker class represents a dedicated web worker and SharedWorker class represents a shared web worker. This article illustrated how both of these types can be used. It also discussed how errors can be handled and how web workers can talk with the web server using XMLHttpRequest.